diff --git a/CLI.md b/CLI.md index 2031ce67..b836fd5b 100644 --- a/CLI.md +++ b/CLI.md @@ -745,6 +745,420 @@ Delete a watchlist: secops watchlist delete --watchlist-id "abc-123-def" ``` +### Integration Management + +#### Marketplace Integrations + +List marketplace integrations: + +```bash +# List all marketplace integration (returns dict with pagination metadata) +secops integration marketplace list + +# List marketplace integration as a direct list (fetches all pages automatically) +secops integration marketplace list --as-list +``` + +Get marketplace integration details: + +```bash +secops integration marketplace get --integration-name "AWSSecurityHub" +``` + +Get marketplace integration diff between installed version and latest version: + +```bash +secops integration marketplace diff --integration-name "AWSSecurityHub" +``` + +Install or update a marketplace integration: + +```bash +# Install with default settings +secops integration marketplace install --integration-name "AWSSecurityHub" + +# Install to staging environment and override any existing ontology mappings +secops integration marketplace install --integration-name "AWSSecurityHub" --staging --override-mapping + +# Installing a currently installed integration with no specified version +# number will update it to the latest version +secops integration marketplace install --integration-name "AWSSecurityHub" + +# Or you can specify a specific version to install +secops integration marketplace install --integration-name "AWSSecurityHub" --version "5.0" +``` + +Uninstall a marketplace integration: + +```bash +secops integration marketplace uninstall --integration-name "AWSSecurityHub" +``` + +#### Integrations + +List integrations: + +```bash +# List all integrations +secops integration integrations list + +# List integrations as a direct list +secops integration integrations list --as-list + +# List with pagination +secops integration integrations list --page-size 50 + +# List with filtering +secops integration integrations list --filter-string "displayName = 'CrowdStrike'" + +# List with ordering +secops integration integrations list --order-by "displayName" +``` + +Get integration details: + +```bash +secops integration integrations get --integration-id "MyIntegration" +``` + +Create a new custom integration: + +```bash +# Create a basic integration +secops integration integrations create --display-name "My Custom Integration" + +# Create in staging mode +secops integration integrations create \ + --display-name "My Custom Integration" \ + --staging + +# Create with all options +secops integration integrations create \ + --display-name "My Custom Integration" \ + --description "Custom integration for internal tooling" \ + --python-version "V3_11" \ + --integration-type "RESPONSE" \ + --staging +``` + +Update an integration: + +```bash +# Update display name +secops integration integrations update \ + --integration-id "MyIntegration" \ + --display-name "Updated Integration Name" + +# Update multiple fields +secops integration integrations update \ + --integration-id "MyIntegration" \ + --display-name "Updated Name" \ + --description "Updated description" \ + --python-version "V3_11" + +# Update with explicit update mask +secops integration integrations update \ + --integration-id "MyIntegration" \ + --display-name "New Name" \ + --update-mask "displayName" + +# Remove dependencies during update +secops integration integrations update \ + --integration-id "MyIntegration" \ + --dependencies-to-remove "old-package" "unused-lib" + +# Set integration to staging mode +secops integration integrations update \ + --integration-id "MyIntegration" \ + --staging +``` + +Update a custom integration definition: + +```bash +# Update a custom integration (supports parameters and dependencies) +secops integration integrations update-custom \ + --integration-id "MyIntegration" \ + --display-name "Updated Custom Integration" \ + --description "Updated custom integration" + +# Update with all options +secops integration integrations update-custom \ + --integration-id "MyIntegration" \ + --display-name "Updated Custom Integration" \ + --python-version "V3_11" \ + --integration-type "EXTENSION" \ + --dependencies-to-remove "old-dep" \ + --staging +``` + +Delete an integration: + +```bash +secops integration integrations delete --integration-id "MyIntegration" +``` + +Download an integration package: + +```bash +# Download integration as a ZIP file +secops integration integrations download \ + --integration-id "MyIntegration" \ + --output-file "/tmp/my-integration.zip" +``` + +Download a Python dependency for a custom integration: + +```bash +# Download a specific dependency +secops integration integrations download-dependency \ + --integration-id "MyIntegration" \ + --dependency-name "requests==2.31.0" +``` + +Export specific items from an integration: + +```bash +# Export specific actions and connectors +secops integration integrations export-items \ + --integration-id "MyIntegration" \ + --output-file "/tmp/export.zip" \ + --actions "action1" "action2" \ + --connectors "connector1" + +# Export jobs and managers +secops integration integrations export-items \ + --integration-id "MyIntegration" \ + --output-file "/tmp/export.zip" \ + --jobs "job1" "job2" \ + --managers "manager1" + +# Export transformers and logical operators +secops integration integrations export-items \ + --integration-id "MyIntegration" \ + --output-file "/tmp/export.zip" \ + --transformers "t1" \ + --logical-operators "lo1" "lo2" +``` + +Get items affected by changes to an integration: + +```bash +secops integration integrations affected-items --integration-id "MyIntegration" +``` + +Get integrations installed on a specific agent: + +```bash +secops integration integrations agent-integrations --agent-id "my-agent-id" +``` + +Get Python dependencies for a custom integration: + +```bash +secops integration integrations dependencies --integration-id "MyIntegration" +``` + +Get agents restricted from running an updated integration: + +```bash +# Check restricted agents for a Python version upgrade +secops integration integrations restricted-agents \ + --integration-id "MyIntegration" \ + --required-python-version "V3_11" + +# Check restricted agents for a push request +secops integration integrations restricted-agents \ + --integration-id "MyIntegration" \ + --required-python-version "V3_11" \ + --push-request +``` + +Get the configuration diff for an integration: + +```bash +# Get diff against marketplace version (default) +secops integration integrations diff --integration-id "MyIntegration" + +# Get diff between staging and production +secops integration integrations diff \ + --integration-id "MyIntegration" \ + --diff-type "Production" + +# Get diff between production and staging +secops integration integrations diff \ + --integration-id "MyIntegration" \ + --diff-type "Staging" +``` + +Transition an integration between staging and production: + +```bash +# Push integration to production +secops integration integrations transition \ + --integration-id "MyIntegration" \ + --target-mode "Production" + +# Push integration to staging +secops integration integrations transition \ + --integration-id "MyIntegration" \ + --target-mode "Staging" +``` + +Example workflow: Create, configure, test, and deploy a custom integration: + +```bash +# 1. Create a new custom integration in staging +secops integration integrations create \ + --display-name "My Custom SIEM Connector" \ + --description "Custom connector for internal SIEM" \ + --python-version "V3_11" \ + --integration-type "RESPONSE" \ + --staging + +# 2. Check its dependencies +secops integration integrations dependencies \ + --integration-id "MyCustomSIEMConnector" + +# 3. View the diff before pushing to production +secops integration integrations diff \ + --integration-id "MyCustomSIEMConnector" \ + --diff-type "Production" + +# 4. Check for restricted agents +secops integration integrations restricted-agents \ + --integration-id "MyCustomSIEMConnector" \ + --required-python-version "V3_11" \ + --push-request + +# 5. Push to production +secops integration integrations transition \ + --integration-id "MyCustomSIEMConnector" \ + --target-mode "Production" + +# 6. Download a backup +secops integration integrations download \ + --integration-id "MyCustomSIEMConnector" \ + --output-file "/tmp/my-siem-connector-backup.zip" + +# 7. Export specific items for sharing +secops integration integrations export-items \ + --integration-id "MyCustomSIEMConnector" \ + --output-file "/tmp/siem-actions.zip" \ + --actions "PingAction" "FetchEvents" +``` + +#### Integration Instances + +List integration instances: + +```bash +# List all instances for an integration +secops integration instances list --integration-name "MyIntegration" + +# List instances as a direct list (fetches all pages automatically) +secops integration instances list --integration-name "MyIntegration" --as-list + +# List with pagination +secops integration instances list --integration-name "MyIntegration" --page-size 50 + +# List with filtering +secops integration instances list --integration-name "MyIntegration" --filter-string "enabled = true" +``` + +Get integration instance details: + +```bash +secops integration instances get \ + --integration-name "MyIntegration" \ + --instance-id "inst123" +``` + +Create a new integration instance: + +```bash +# Create basic integration instance +secops integration instances create \ + --integration-name "MyIntegration" \ + --display-name "Production Instance" \ + --environment "production" + +# Create with description and custom ID +secops integration instances create \ + --integration-name "MyIntegration" \ + --display-name "Test Instance" \ + --environment "test" \ + --description "Testing environment instance" \ + --instance-id "test-inst-001" + +# Create with configuration +secops integration instances create \ + --integration-name "MyIntegration" \ + --display-name "Configured Instance" \ + --environment "production" \ + --config '{"api_key":"secret123","region":"us-east1"}' +``` + +Update an integration instance: + +```bash +# Update display name +secops integration instances update \ + --integration-name "MyIntegration" \ + --instance-id "inst123" \ + --display-name "Updated Instance Name" + +# Update configuration +secops integration instances update \ + --integration-name "MyIntegration" \ + --instance-id "inst123" \ + --config '{"api_key":"newsecret456","region":"us-west1"}' + +# Update multiple fields with update mask +secops integration instances update \ + --integration-name "MyIntegration" \ + --instance-id "inst123" \ + --display-name "New Name" \ + --description "New description" \ + --update-mask "displayName,description" +``` + +Delete an integration instance: + +```bash +secops integration instances delete \ + --integration-name "MyIntegration" \ + --instance-id "inst123" +``` + +Test an integration instance: + +```bash +# Test the instance configuration +secops integration instances test \ + --integration-name "MyIntegration" \ + --instance-id "inst123" +``` + +Get affected items: + +```bash +# Get items affected by this instance +secops integration instances get-affected-items \ + --integration-name "MyIntegration" \ + --instance-id "inst123" +``` + +Get default instance: + +```bash +# Get the default integration instance +secops integration instances get-default \ + --integration-name "MyIntegration" +``` + + + ### Rule Management List detection rules: @@ -896,7 +1310,6 @@ secops curated-rule search-detections \ --end-time "2024-01-31T23:59:59Z" \ --list-basis "DETECTION_TIME" \ --page-size 50 - ``` List all curated rule sets: @@ -1612,39 +2025,7 @@ secops reference-list create \ secops parser list # Get details of a specific parser -secops parser get --log-type "WINDOWS" --id "pa_12345" - -# Create a custom parser for a new log format -secops parser create \ - --log-type "CUSTOM_APPLICATION" \ - --parser-code-file "/path/to/custom_parser.conf" \ - --validated-on-empty-logs - -# Copy an existing parser as a starting point -secops parser copy --log-type "OKTA" --id "pa_okta_base" - -# Activate your custom parser -secops parser activate --log-type "CUSTOM_APPLICATION" --id "pa_new_custom" - -# If needed, deactivate and delete old parser -secops parser deactivate --log-type "CUSTOM_APPLICATION" --id "pa_old_custom" -secops parser delete --log-type "CUSTOM_APPLICATION" --id "pa_old_custom" -``` - -### Complete Parser Workflow Example: Retrieve, Run, and Ingest - -This example demonstrates the complete workflow of retrieving an OKTA parser, running it against a sample log, and ingesting the parsed UDM event: - -```bash -# Step 1: List OKTA parsers to find an active one -secops parser list --log-type "OKTA" > okta_parsers.json - -# Extract the first parser ID (you can use jq or grep) -PARSER_ID=$(cat okta_parsers.json | jq -r '.[0].name' | awk -F'/' '{print $NF}') -echo "Using parser: $PARSER_ID" - -# Step 2: Get the parser details and save to a file -secops parser get --log-type "OKTA" --id "$PARSER_ID" > parser_details.json +secops parser get --log-type "WINDOWS" --id "$PARSER_ID" > parser_details.json # Extract and decode the parser code (base64 encoded in 'cbn' field) cat parser_details.json | jq -r '.cbn' | base64 -d > okta_parser.conf @@ -1782,7 +2163,7 @@ secops feed update --id "feed-123" --display-name "Updated Feed Name" secops feed update --id "feed-123" --details '{"httpSettings":{"uri":"https://example.com/updated-feed","sourceType":"FILES"}}' # Update both display name and details -secops feed update --id "feed-123" --display-name "Updated Name" --details '{"httpSettings":{"uri":"https://example.com/updated-feed"}}' +secops feed update --id "feed-123" --display-name "New Name" --details '{"httpSettings":{"uri":"https://example.com/updated-feed"}}' ``` Enable and disable feeds: @@ -1923,4 +2304,5 @@ secops dashboard-query get --id query-id ## Conclusion -The SecOps CLI provides a powerful way to interact with Google Security Operations products directly from your terminal. For more detailed information about the SDK capabilities, refer to the [main README](README.md). \ No newline at end of file +The SecOps CLI provides a powerful way to interact with Google Security Operations products directly from your terminal. For more detailed information about the SDK capabilities, refer to the [main README](README.md). + diff --git a/README.md b/README.md index 34e9fe8a..dba1b272 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +from tests.chronicle.test_rule_integration import chronicle + # Google SecOps SDK for Python [![PyPI version](https://img.shields.io/pypi/v/secops.svg)](https://pypi.org/project/secops/) @@ -1658,13 +1660,13 @@ udm_mapping = chronicle.generate_udm_mapping(log_type="WINDOWS_AD") print(udm_mapping) ``` -## Parser Management +### Parser Management Chronicle parsers are used to process and normalize raw log data into Chronicle's Unified Data Model (UDM) format. Parsers transform various log formats (JSON, XML, CEF, etc.) into a standardized structure that enables consistent querying and analysis across different data sources. The SDK provides comprehensive support for managing Chronicle parsers: -### Creating Parsers +#### Creating Parsers Create new parser: @@ -1710,7 +1712,7 @@ parser_id = parser.get("name", "").split("/")[-1] print(f"Parser ID: {parser_id}") ``` -### Managing Parsers +#### Managing Parsers Retrieve, list, copy, activate/deactivate, and delete parsers: @@ -1754,7 +1756,7 @@ chronicle.activate_release_candidate_parser(log_type=log_type, id="pa_release_ca > **Note:** Parsers work in conjunction with log ingestion. When you ingest logs using `chronicle.ingest_log()`, Chronicle automatically applies the appropriate parser based on the log type to transform your raw logs into UDM format. If you're working with custom log formats, you may need to create or configure custom parsers first. -### Run Parser against sample logs +#### Run Parser against sample logs Run the parser on one or more sample logs: @@ -1844,7 +1846,7 @@ The `run_parser` function includes comprehensive validation: - Enforces size limits (10MB per log, 50MB total, max 1000 logs) - Provides detailed error messages for different failure scenarios -### Complete Parser Workflow Example +#### Complete Parser Workflow Example Here's a complete example that demonstrates retrieving a parser, running it against a log, and ingesting the parsed UDM event: @@ -1898,13 +1900,13 @@ This workflow is useful for: - Re-processing logs with updated parsers - Debugging parsing issues -## Parser Extension +### Parser Extension Parser extensions provide a flexible way to extend the capabilities of existing default (or custom) parsers without replacing them. The extensions let you customize the parser pipeline by adding new parsing logic, extracting and transforming fields, and updating or removing UDM field mappings. The SDK provides comprehensive support for managing Chronicle parser extensions: -### List Parser Extensions +#### List Parser Extensions List parser extensions for a log type: @@ -1915,7 +1917,7 @@ extensions = chronicle.list_parser_extensions(log_type) print(f"Found {len(extensions["parserExtensions"])} parser extensions for log type: {log_type}") ``` -### Create a new parser extension +#### Create a new parser extension Create new parser extension using either CBN snippet, field extractor or dynamic parsing: @@ -1938,7 +1940,7 @@ field_extractor = { chronicle.create_parser_extension(log_type, field_extractor=field_extractor) ``` -### Get parser extension +#### Get parser extension Get parser extension details: @@ -1951,7 +1953,7 @@ extension = chronicle.get_parser_extension(log_type, extension_id) print(extension) ``` -### Activate Parser Extension +#### Activate Parser Extension Activate parser extension: @@ -1962,7 +1964,7 @@ extension_id = "1234567890" chronicle.activate_parser_extension(log_type, extension_id) ``` -### Delete Parser Extension +#### Delete Parser Extension Delete parser extension: @@ -1973,9 +1975,9 @@ extension_id = "1234567890" chronicle.delete_parser_extension(log_type, extension_id) ``` -## Watchlist Management +### Watchlist Management -### Creating a Watchlist +#### Creating a Watchlist Create a new watchlist: @@ -1988,7 +1990,7 @@ watchlist = chronicle.create_watchlist( ) ``` -### Updating a Watchlist +#### Updating a Watchlist Update a watchlist by ID: @@ -2003,7 +2005,7 @@ updated_watchlist = chronicle.update_watchlist( ) ``` -### Deleting a Watchlist +#### Deleting a Watchlist Delete a watchlist by ID: @@ -2011,7 +2013,7 @@ Delete a watchlist by ID: chronicle.delete_watchlist("acb-123-def", force=True) ``` -### Getting a Watchlist +#### Getting a Watchlist Get a watchlist by ID: @@ -2019,7 +2021,7 @@ Get a watchlist by ID: watchlist = chronicle.get_watchlist("acb-123-def") ``` -### List all Watchlists +#### List all Watchlists List all watchlists: @@ -2035,11 +2037,11 @@ for watchlist in watchlists: print(f"Watchlist: {watchlist.get('displayName')}") ``` -## Rule Management +### Rule Management The SDK provides comprehensive support for managing Chronicle detection rules: -### Creating Rules +#### Creating Rules Create new detection rules using YARA-L 2.0 syntax: @@ -2067,7 +2069,7 @@ rule_id = rule.get("name", "").split("/")[-1] print(f"Rule ID: {rule_id}") ``` -### Managing Rules +#### Managing Rules Retrieve, list, update, enable/disable, and delete rules: @@ -2098,7 +2100,7 @@ deployment = chronicle.enable_rule(rule_id, enabled=False) # Disable chronicle.delete_rule(rule_id) ``` -### Rule Deployment +#### Rule Deployment Manage a rule's deployment (enabled/alerting/archive state and run frequency): @@ -2127,7 +2129,7 @@ chronicle.update_rule_deployment( ) ``` -### Searching Rules +#### Searching Rules Search for rules using regular expressions: @@ -2143,7 +2145,7 @@ mitre_rules = chronicle.search_rules("T1055") print(f"Found {len(mitre_rules.get('rules', []))} rules mentioning T1055 technique") ``` -### Testing Rules +#### Testing Rules Test rules against historical data to validate their effectiveness before deployment: @@ -2207,7 +2209,7 @@ for result in test_results: print(f"Finished testing. Found {detection_count} detection(s).") ``` -# Extract just the UDM events for programmatic processing +#### Extract just the UDM events for programmatic processing ```python udm_events = [] for result in chronicle.run_rule_test(rule_text, start_time, end_time, max_results=100): @@ -2229,7 +2231,7 @@ for event in udm_events: print(f"Event type: {metadata.get('eventType')}") ``` -### Retrohunts +#### Retrohunts Run rules against historical data to find past matches: @@ -2252,7 +2254,7 @@ state = retrohunt_status.get("state", "") retrohunts = chronicle.list_retrohunts(rule_id) ``` -### Detections and Errors +#### Detections and Errors Monitor rule detections and execution errors: @@ -2282,7 +2284,7 @@ for error in errors.get("ruleExecutionErrors", []): print(f"Error: {error_message}, Time: {create_time}") ``` -### Rule Alerts +#### Rule Alerts Search for alerts generated by rules: @@ -2340,7 +2342,7 @@ for rule_alert in alerts_response.get('ruleAlerts', []): If `tooManyAlerts` is True in the response, consider narrowing your search criteria using a smaller time window or more specific filters. -### Curated Rule Sets +#### Curated Rule Sets Query curated rules: @@ -2503,7 +2505,7 @@ chronicle.update_curated_rule_set_deployment( ``` -### Rule Validation +#### Rule Validation Validate a YARA-L2 rule before creating or updating it: @@ -2535,7 +2537,7 @@ else: print(f"Error at line {result.position['startLine']}, column {result.position['startColumn']}") ``` -### Rule Exclusions +#### Rule Exclusions Rule Exclusions allow you to exclude specific events from triggering detections in Chronicle. They are useful for filtering out known false positives or excluding test/development traffic from production detections. @@ -2594,7 +2596,7 @@ activity = chronicle.compute_rule_exclusion_activity( ) ``` -### Featured Content Rules +#### Featured Content Rules Featured content rules are pre-built detection rules available in the Chronicle Content Hub. These curated rules help you quickly deploy detections without writing custom rules. @@ -2628,11 +2630,11 @@ filtered_rules = chronicle.list_featured_content_rules( ) ``` -## Data Tables and Reference Lists +### Data Tables and Reference Lists Chronicle provides two ways to manage and reference structured data in detection rules: Data Tables and Reference Lists. These can be used to maintain lists of trusted/suspicious entities, mappings of contextual information, or any other structured data useful for detection. -### Data Tables +#### Data Tables Data Tables are collections of structured data with defined columns and data types. They can be referenced in detection rules to enhance your detections with additional context. @@ -2741,11 +2743,11 @@ chronicle.update_data_table_rows( chronicle.delete_data_table("suspicious_ips", force=True) # force=True deletes even if it has rows ``` -### Reference Lists +#### Reference Lists Reference Lists are simple lists of values (strings, CIDR blocks, or regex patterns) that can be referenced in detection rules. They are useful for maintaining whitelists, blacklists, or any other categorized sets of values. -#### Creating Reference Lists +##### Creating Reference Lists ```python from secops.chronicle.reference_list import ReferenceListSyntaxType, ReferenceListView @@ -2777,7 +2779,7 @@ regex_list = chronicle.create_reference_list( ) ``` -#### Managing Reference Lists +##### Managing Reference Lists ```python # List all reference lists (basic view without entries) @@ -2805,11 +2807,11 @@ chronicle.update_reference_list( ``` -### Using in YARA-L Rules +#### Using in YARA-L Rules Both Data Tables and Reference Lists can be referenced in YARA-L detection rules. -#### Using Data Tables in Rules +##### Using Data Tables in Rules ``` rule detect_with_data_table { @@ -2849,7 +2851,7 @@ rule detect_with_reference_list { } ``` -## Gemini AI +### Gemini AI You can use Chronicle's Gemini AI to get security insights, generate detection rules, explain security concepts, and more: @@ -2896,7 +2898,7 @@ for action in response.suggested_actions: print(f"Action URI: {action.navigation.target_uri}") ``` -### Response Content Methods +#### Response Content Methods The `GeminiResponse` class provides several methods to work with response content: @@ -2907,7 +2909,7 @@ The `GeminiResponse` class provides several methods to work with response conten These methods help you work with different types of content in a structured way. -### Accessing Raw API Response +#### Accessing Raw API Response For advanced use cases or debugging, you can access the raw API response: @@ -2928,7 +2930,7 @@ if "responses" in raw_response: This gives you direct access to the original API response format, which can be useful for accessing advanced features or troubleshooting. -### Manual Opt-In +#### Manual Opt-In If your account has sufficient permissions, you can manually opt-in to Gemini before using it: @@ -2946,7 +2948,7 @@ response = chronicle.gemini("What is Windows event ID 4625?") This can be useful in environments where you want to explicitly control when the opt-in happens. -### Generate Detection Rules +#### Generate Detection Rules Chronicle Gemini can generate YARA-L rules for detection: @@ -2967,7 +2969,7 @@ if code_blocks: print("Rule can be opened in editor:", rule_editor_url) ``` -### Get Intel Information +#### Get Intel Information Get detailed information about malware, threat actors, files, vulnerabilities: @@ -2980,7 +2982,7 @@ cve_explanation = cve_response.get_text_content() print("CVE explanation:", cve_explanation) ``` -### Maintain Conversation Context +#### Maintain Conversation Context You can maintain conversation context by reusing the same conversation ID: @@ -3000,7 +3002,7 @@ followup_response = chronicle.gemini( # Gemini will remember the context of the previous question about DDoS ``` -## Feed Management +### Feed Management Feeds are used to ingest data into Chronicle. The SDK provides methods to manage feeds. @@ -3074,11 +3076,11 @@ The Feed API supports different feed types such as HTTP, HTTPS Push, and S3 buck > **Note**: Secret generation is only available for certain feed types that require authentication. -## Chronicle Dashboard +### Chronicle Dashboard The Chronicle Dashboard API provides methods to manage native dashboards and dashboard charts in Chronicle. -### Create Dashboard +#### Create Dashboard ```python # Create a dashboard dashboard = chronicle.create_dashboard( @@ -3090,7 +3092,7 @@ dashboard_id = dashboard["name"].split("/")[-1] print(f"Created dashboard with ID: {dashboard_id}") ``` -### Get Specific Dashboard Details +#### Get Specific Dashboard Details ```python # Get a specific dashboard dashboard = chronicle.get_dashboard( @@ -3100,7 +3102,7 @@ dashboard = chronicle.get_dashboard( print(f"Dashboard Details: {dashboard}") ``` -### List Dashboards +#### List Dashboards ```python dashboards = chronicle.list_dashboards() for dashboard in dashboards.get("nativeDashboards", []): @@ -3119,7 +3121,7 @@ if "nextPageToken" in dashboards: ) ``` -### Update existing dashboard details +#### Update existing dashboard details ```python filters = [ { @@ -3151,7 +3153,7 @@ updated_dashboard = chronicle.update_dashboard( print(f"Updated dashboard: {updated_dashboard['displayName']}") ``` -### Duplicate existing dashboard +#### Duplicate existing dashboard ```python # Duplicate a dashboard duplicate = chronicle.duplicate_dashboard( @@ -3199,7 +3201,7 @@ dashboards = chronicle.export_dashboard(dashboard_names=[""]) ``` -### Add Chart to existing dashboard +#### Add Chart to existing dashboard ```python # Define chart configuration query = """ @@ -3242,7 +3244,7 @@ chart = chronicle.add_chart( ) ``` -### Get Dashboard Chart Details +#### Get Dashboard Chart Details ```python # Get dashboard chart details dashboard_chart = chronicle.get_chart( @@ -3251,7 +3253,7 @@ dashboard_chart = chronicle.get_chart( print(f"Dashboard Chart Details: {dashboard_chart}") ``` -### Edit Dashboard Chart +#### Edit Dashboard Chart ```python # Dashboard Query updated details updated_dashboard_query={ @@ -3279,7 +3281,7 @@ updated_chart = chronicle.edit_chart( print(f"Updated dashboard chart: {updated_chart}") ``` -### Remove Chart from existing dashboard +#### Remove Chart from existing dashboard ```python # Remove chart from dashboard chronicle.remove_chart( @@ -3288,18 +3290,18 @@ chronicle.remove_chart( ) ``` -### Delete existing dashboard +#### Delete existing dashboard ```python # Delete a dashboard chronicle.delete_dashboard(dashboard_id="dashboard-id-here") print("Dashboard deleted successfully") ``` -## Dashboard Query +### Dashboard Query The Chronicle Dashboard Query API provides methods to execute dashboard queries without creating a dashboard and get details of dashboard query. -### Execute Dashboard Query +#### Execute Dashboard Query ```python # Define query and time interval query = """ @@ -3331,7 +3333,7 @@ for result in results.get("results", []): print(result) ``` -### Get Dashboard Query details +#### Get Dashboard Query details ```python # Get dashboard query details dashboard_query = chronicle.get_dashboard_query( @@ -3340,6 +3342,448 @@ dashboard_query = chronicle.get_dashboard_query( print(f"Dashboard Query Details: {dashboard_query}") ``` +## SOAR Modules + +The SDK provides functions to interact with SOAR modules in Google SecOps. These are available under the `soar` namespace on the `ChronicleClient` instance, e.g. `chronicle.soar.(...)`. This covers the full lifecycle of integrations, integration instances, actions, connectors, jobs, managers, and playbooks. + +### Integration Management + +#### Marketplace Integrations + +List available marketplace integrations: + +```python +# Get all available marketplace integrations +integrations = chronicle.soar.list_marketplace_integrations() +for integration in integrations.get("marketplaceIntegrations", []): + integration_title = integration.get("title") + integration_id = integration.get("name", "").split("/")[-1] + integration_version = integration.get("version", "") + documentation_url = integration.get("documentationUri", "") + +# Get all integrations as a list +integrations = chronicle.soar.list_marketplace_integrations(as_list=True) + +# Get all currently installed integrations +integrations = chronicle.soar.list_marketplace_integrations(filter_string="installed = true") + +# Get all installed integrations with updates available +integrations = chronicle.soar.list_marketplace_integrations( + filter_string="installed = true AND updateAvailable = true" +) + +# Specify use of V1 Alpha API version +integrations = chronicle.soar.list_marketplace_integrations(api_version=APIVersion.V1ALPHA) +``` + +Get a specific marketplace integration: + +```python +integration = chronicle.soar.get_marketplace_integration("AWSSecurityHub") +``` + +Get the diff between the currently installed version and the latest +available version of an integration: + +```python +diff = chronicle.soar.get_marketplace_integration_diff("AWSSecurityHub") +``` + +Install or update a marketplace integration: + +```python +# Install an integration with the default settings +integration_name = "AWSSecurityHub" +integration = chronicle.soar.install_marketplace_integration(integration_name) + +# Install to staging environment and override any existing ontology mappings +integration = chronicle.soar.install_marketplace_integration( + integration_name, + staging=True, + override_mapping=True, +) + +# Installing a currently installed integration with no specified version +# number will update it to the latest version +integration = chronicle.soar.install_marketplace_integration(integration_name) + +# Or you can specify a specific version to install +integration = chronicle.soar.install_marketplace_integration( + integration_name, + version="5.0", +) +``` + +Uninstall a marketplace integration: + +```python +chronicle.soar.uninstall_marketplace_integration("AWSSecurityHub") +``` + +#### Integrations + +List all installed integrations: + +```python +# Get all integrations +integrations = chronicle.soar.list_integrations() +for i in integrations.get("integrations", []): + integration_id = i["identifier"] + integration_display_name = i["displayName"] + integration_type = i["type"] + +# Get all integrations as a list +integrations = chronicle.soar.list_integrations(as_list=True) + +for i in integrations: + integration = chronicle.soar.get_integration(i["identifier"]) + if integration.get("parameters"): + print(json.dumps(integration, indent=2)) + +# Get integrations ordered by display name +integrations = chronicle.soar.list_integrations(order_by="displayName", as_list=True) +``` + +Get details of a specific integration: + +```python +integration = chronicle.soar.get_integration("AWSSecurityHub") +``` + +Create an integration: + +```python +from secops.chronicle.models import ( + IntegrationParam, + IntegrationParamType, + IntegrationType, + PythonVersion, +) + +integration = chronicle.soar.create_integration( + display_name="MyNewIntegration", + staging=True, + description="This is my integration", + python_version=PythonVersion.PYTHON_3_11, + parameters=[ + IntegrationParam( + display_name="AWS Access Key", + property_name="aws_access_key", + type=IntegrationParamType.STRING, + description="AWS access key for authentication", + mandatory=True, + ), + IntegrationParam( + display_name="AWS Secret Key", + property_name="aws_secret_key", + type=IntegrationParamType.PASSWORD, + description="AWS secret key for authentication", + mandatory=False, + ), + ], + categories=["Cloud Security", "Cloud", "Security"], + integration_type=IntegrationType.RESPONSE, +) +``` + +Update an integration: + +```python +from secops.chronicle.models import IntegrationParam, IntegrationParamType + +updated_integration = chronicle.soar.update_integration( + integration_name="MyNewIntegration", + display_name="Updated Integration Name", + description="Updated description", + parameters=[ + IntegrationParam( + display_name="AWS Region", + property_name="aws_region", + type=IntegrationParamType.STRING, + description="AWS region to use", + mandatory=True, + ), + ], + categories=["Cloud Security", "Cloud", "Security"], +) +``` + +Update a custom integration (including its scripts and dependencies): + +```python +result = chronicle.soar.update_custom_integration( + integration_name="MyNewIntegration", + display_name="Updated Integration Name", + description="Updated description", + dependencies_to_remove=["old-lib==1.0.0"], +) +# result contains "successful", "integration", and optionally "dependencies" +``` + +Delete an integration: + +```python +chronicle.soar.delete_integration("MyNewIntegration") +``` + +Download an entire integration as a bytes object and save it as a `.zip` file. +This includes all the integration details, parameters, and actions in a format +that can be re-uploaded to Chronicle or used for backup purposes: + +```python +integration_bytes = chronicle.soar.download_integration("MyIntegration") +with open("MyIntegration.zip", "wb") as f: + f.write(integration_bytes) +``` + +Export selected items from an integration (e.g. only specific actions) as a `.zip` file: + +```python +# Export only actions with IDs 1 and 2 from the integration +export_bytes = chronicle.soar.export_integration_items( + integration_name="AWSSecurityHub", + actions=["1", "2"], # IDs of the actions to export +) +with open("AWSSecurityHub_Export.zip", "wb") as f: + f.write(export_bytes) + +# Export multiple item types at once +export_bytes = chronicle.soar.export_integration_items( + integration_name="AWSSecurityHub", + actions=["1", "2"], + connectors=["3"], + jobs=["4", "5"], +) +``` + +Get dependencies for an integration: + +```python +dependencies = chronicle.soar.get_integration_dependencies("AWSSecurityHub") +for dep in dependencies.get("dependencies", []): + parts = dep.split("-") + dependency_name = parts[0] + dependency_version = parts[1] if len(parts) > 1 else "latest" + print(f"Dependency: {dependency_name}, Version: {dependency_version}") +``` + +Force a dependency download for an integration: + +```python +# Install a specific version of a dependency +chronicle.soar.download_integration_dependency( + "MyIntegration", + "boto3==1.42.44", +) + +# Install the latest version of a dependency +chronicle.soar.download_integration_dependency( + "MyIntegration", + "boto3", +) +``` + +Get remote agents that would be restricted from running an updated version of the integration: + +```python +from secops.chronicle.models import PythonVersion + +agents = chronicle.soar.get_integration_restricted_agents( + integration_name="AWSSecurityHub", + required_python_version=PythonVersion.PYTHON_3_11, +) +``` + +Get the integrations currently installed on a specific agent: + +```python +agent_integrations = chronicle.soar.get_agent_integrations(agent_id="my-agent-id") +``` + +Get items (connector instances, job instances, playbooks) affected by changes to an integration: + +```python +affected = chronicle.soar.get_integration_affected_items("AWSSecurityHub") +``` + +Get integration diff between two versions of an integration: + +```python +from secops.chronicle.models import DiffType + +# Get the diff between the commercial version and the current version in the environment +diff = chronicle.soar.get_integration_diff( + integration_name="AWSSecurityHub", + diff_type=DiffType.COMMERCIAL, +) + +# Get the difference between the staging integration and its matching production version +diff = chronicle.soar.get_integration_diff( + integration_name="AWSSecurityHub", + diff_type=DiffType.PRODUCTION, +) + +# Get the difference between the production integration and its corresponding staging version +diff = chronicle.soar.get_integration_diff( + integration_name="AWSSecurityHub", + diff_type=DiffType.STAGING, +) +``` + +Transition an integration to staging or production environment: + +```python +from secops.chronicle.models import TargetMode + +# Transition to staging environment +chronicle.soar.transition_integration( + integration_name="AWSSecurityHub", + target_mode=TargetMode.STAGING, +) + +# Transition to production environment +chronicle.soar.transition_integration( + integration_name="AWSSecurityHub", + target_mode=TargetMode.PRODUCTION, +) +``` + +#### Integration Instances + +List all instances for a specific integration: + +```python +# Get all instances for an integration +instances = chronicle.soar.list_integration_instances("MyIntegration") +for instance in instances.get("integrationInstances", []): + print(f"Instance: {instance.get('displayName')}, ID: {instance.get('name')}") + print(f"Environment: {instance.get('environment')}") + +# Get all instances as a list +instances = chronicle.soar.list_integration_instances("MyIntegration", as_list=True) + +# Get instances for a specific environment +instances = chronicle.soar.list_integration_instances( + "MyIntegration", + filter_string="environment = 'production'", +) +``` + +Get details of a specific integration instance: + +```python +instance = chronicle.soar.get_integration_instance( + integration_name="MyIntegration", + integration_instance_id="ii1", +) +print(f"Display Name: {instance.get('displayName')}") +print(f"Environment: {instance.get('environment')}") +print(f"Agent: {instance.get('agent')}") +``` + +Create a new integration instance: + +```python +from secops.chronicle.models import IntegrationInstanceParameter + +# Create instance with required fields only +new_instance = chronicle.soar.create_integration_instance( + integration_name="MyIntegration", + environment="production", +) + +# Create instance with all fields +new_instance = chronicle.soar.create_integration_instance( + integration_name="MyIntegration", + environment="production", + display_name="Production Instance", + description="Main production integration instance", + parameters=[ + IntegrationInstanceParameter(value="api_key_value"), + IntegrationInstanceParameter(value="https://api.example.com"), + ], + agent="agent-123", +) +``` + +Update an existing integration instance: + +```python +from secops.chronicle.models import IntegrationInstanceParameter + +# Update instance display name +updated_instance = chronicle.soar.update_integration_instance( + integration_name="MyIntegration", + integration_instance_id="ii1", + display_name="Updated Production Instance", +) + +# Update multiple fields including parameters +updated_instance = chronicle.soar.update_integration_instance( + integration_name="MyIntegration", + integration_instance_id="ii1", + display_name="Updated Instance", + description="Updated description", + environment="staging", + parameters=[ + IntegrationInstanceParameter(value="new_api_key"), + ], +) + +# Use a custom update mask to restrict which fields are patched +updated_instance = chronicle.soar.update_integration_instance( + integration_name="MyIntegration", + integration_instance_id="ii1", + display_name="New Name", + update_mask="displayName", +) +``` + +Delete an integration instance: + +```python +chronicle.soar.delete_integration_instance( + integration_name="MyIntegration", + integration_instance_id="ii1", +) +``` + +Execute a connectivity test for an integration instance: + +```python +# Test if the instance can connect to the third-party service +test_result = chronicle.soar.execute_integration_instance_test( + integration_name="MyIntegration", + integration_instance_id="ii1", +) +print(f"Test Successful: {test_result.get('successful')}") +print(f"Message: {test_result.get('message')}") +``` + +Get affected items (playbooks) that depend on an integration instance: + +```python +# Perform impact analysis before deleting or modifying an instance +affected_items = chronicle.soar.get_integration_instance_affected_items( + integration_name="MyIntegration", + integration_instance_id="ii1", +) +for playbook in affected_items.get("affectedPlaybooks", []): + print(f"Playbook: {playbook.get('displayName')}") + print(f" ID: {playbook.get('name')}") +``` + +Get the default integration instance: + +```python +# Get the system default configuration for a commercial product +default_instance = chronicle.soar.get_default_integration_instance( + integration_name="AWSSecurityHub", +) +print(f"Default Instance: {default_instance.get('displayName')}") +print(f"Environment: {default_instance.get('environment')}") +``` + ## Error Handling The SDK defines several custom exceptions: @@ -3357,7 +3801,7 @@ except SecOpsError as e: print(f"General error: {e}") ``` -## Value Type Detection +### Value Type Detection The SDK automatically detects the most common entity types when using the `summarize_entity` function: - IP addresses (IPv4 and IPv6) diff --git a/api_module_mapping.md b/api_module_mapping.md index 6e91dd72..13fe821e 100644 --- a/api_module_mapping.md +++ b/api_module_mapping.md @@ -7,408 +7,1040 @@ Following shows mapping between SecOps [REST Resource](https://cloud.google.com/ ## Implementation Statistics - **v1:** 17 endpoints implemented -- **v1beta:** 10 endpoints implemented -- **v1alpha:** 113 endpoints implemented +- **v1beta:** 98 endpoints implemented +- **v1alpha:** 203 endpoints implemented ## Endpoint Mapping -| REST Resource | Version | secops-wrapper module | CLI Command | -|--------------------------------------------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------|------------------------------------------------| -| dataAccessLabels.create | v1 | | | -| dataAccessLabels.delete | v1 | | | -| dataAccessLabels.get | v1 | | | -| dataAccessLabels.list | v1 | | | -| dataAccessLabels.patch | v1 | | | -| dataAccessScopes.create | v1 | | | -| dataAccessScopes.delete | v1 | | | -| dataAccessScopes.get | v1 | | | -| dataAccessScopes.list | v1 | | | -| dataAccessScopes.patch | v1 | | | -| get | v1 | | | -| operations.cancel | v1 | | | -| operations.delete | v1 | | | -| operations.get | v1 | | | -| operations.list | v1 | | | -| referenceLists.create | v1 | chronicle.reference_list.create_reference_list | secops reference-list create | -| referenceLists.get | v1 | chronicle.reference_list.get_reference_list | secops reference-list get | -| referenceLists.list | v1 | chronicle.reference_list.list_reference_lists | secops reference-list list | -| referenceLists.patch | v1 | chronicle.reference_list.update_reference_list | secops reference-list update | -| rules.create | v1 | chronicle.rule.create_rule | secops rule create | -| rules.delete | v1 | chronicle.rule.delete_rule | secops rule delete | -| rules.deployments.list | v1 | | | -| rules.get | v1 | chronicle.rule.get_rule | secops rule get | -| rules.getDeployment | v1 | | | -| rules.list | v1 | chronicle.rule.list_rules | secops rule list | -| rules.listRevisions | v1 | | | -| rules.patch | v1 | chronicle.rule.update_rule | secops rule update | -| rules.retrohunts.create | v1 | chronicle.rule_retrohunt.create_retrohunt | secops rule-retrohunt create | -| rules.retrohunts.get | v1 | chronicle.rule_retrohunt.get_retrohunt | secops rule-retrohunt get | -| rules.retrohunts.list | v1 | chronicle.rule_retrohunt.list_retrohunts | secops rule-retrohunt list | -| rules.updateDeployment | v1 | chronicle.rule.enable_rule | secops rule enable | -| watchlists.create | v1 | chronicle.watchlist.create_watchlist | secops watchlist create | -| watchlists.delete | v1 | chronicle.watchlist.delete_watchlist | secops watchlist delete | -| watchlists.get | v1 | chronicle.watchlist.get_watchlist | secops watchlist get | -| watchlists.list | v1 | chronicle.watchlist.list_watchlists | secops watchlist list | -| watchlists.patch | v1 | chronicle.watchlist.update_watchlist | secops watchlist update | -| dataAccessLabels.create | v1beta | | | -| dataAccessLabels.delete | v1beta | | | -| dataAccessLabels.get | v1beta | | | -| dataAccessLabels.list | v1beta | | | -| dataAccessLabels.patch | v1beta | | | -| dataAccessScopes.create | v1beta | | | -| dataAccessScopes.delete | v1beta | | | -| dataAccessScopes.get | v1beta | | | -| dataAccessScopes.list | v1beta | | | -| dataAccessScopes.patch | v1beta | | | -| get | v1beta | | | -| operations.cancel | v1beta | | | -| operations.delete | v1beta | | | -| operations.get | v1beta | | | -| operations.list | v1beta | | | -| referenceLists.create | v1beta | | | -| referenceLists.get | v1beta | | | -| referenceLists.list | v1beta | | | -| referenceLists.patch | v1beta | | | -| rules.create | v1beta | | | -| rules.delete | v1beta | | | -| rules.deployments.list | v1beta | | | -| rules.get | v1beta | | | -| rules.getDeployment | v1beta | | | -| rules.list | v1beta | | | -| rules.listRevisions | v1beta | | | -| rules.patch | v1beta | | | -| rules.retrohunts.create | v1beta | | | -| rules.retrohunts.get | v1beta | | | -| rules.retrohunts.list | v1beta | | | -| rules.updateDeployment | v1beta | | | -| watchlists.create | v1beta | | | -| watchlists.delete | v1beta | | | -| watchlists.get | v1beta | | | -| watchlists.list | v1beta | | | -| watchlists.patch | v1beta | | | -| cases.executeBulkAddTag | v1beta | chronicle.case.execute_bulk_add_tag | secops case bulk-add-tag | -| cases.executeBulkAssign | v1beta | chronicle.case.execute_bulk_assign | secops case bulk-assign | -| cases.executeBulkChangePriority | v1beta | chronicle.case.execute_bulk_change_priority | secops case bulk-change-priority | -| cases.executeBulkChangeStage | v1beta | chronicle.case.execute_bulk_change_stage | secops case bulk-change-stage | -| cases.executeBulkClose | v1beta | chronicle.case.execute_bulk_close | secops case bulk-close | -| cases.executeBulkReopen | v1beta | chronicle.case.execute_bulk_reopen | secops case bulk-reopen | -| cases.get | v1beta | chronicle.case.get_case | secops case get | -| cases.list | v1beta | chronicle.case.list_cases | secops case list | -| cases.merge | v1beta | chronicle.case.merge_cases | secops case merge | -| cases.patch | v1beta | chronicle.case.patch_case | secops case update | -| analytics.entities.analyticValues.list | v1alpha | | | -| analytics.list | v1alpha | | | -| batchValidateWatchlistEntities | v1alpha | | | -| bigQueryAccess.provide | v1alpha | | | -| bigQueryExport.provision | v1alpha | | | -| cases.countPriorities | v1alpha | | | -| contentHub.featuredContentRules.list | v1alpha | chronicle.featured_content_rules.list_featured_content_rules | secops featured-content-rules list | -| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.batchUpdate | v1alpha | chronicle.rule_set.batch_update_curated_rule_set_deployments | | -| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.patch | v1alpha | chronicle.rule_set.update_curated_rule_set_deployment | secops curated-rule rule-set-deployment update | -| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.list | v1alpha | chronicle.rule_set.list_curated_rule_set_deployments | secops curated-rule rule-set-deployment list | -| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.get | v1alpha | chronicle.rule_set.get_curated_rule_set_deployment
chronicle.rule_set.get_curated_rule_set_deployment_by_name | secops curated-rule rule-set-deployment get | -| curatedRuleSetCategories.curatedRuleSets.get | v1alpha | chronicle.rule_set.get_curated_rule_set | secops curated-rule rule-set get | -| curatedRuleSetCategories.curatedRuleSets.list | v1alpha | chronicle.rule_set.list_curated_rule_sets | secops curated-rule rule-set list | -| curatedRuleSetCategories.get | v1alpha | chronicle.rule_set.get_curated_rule_set_category | secops curated-rule rule-set-category get | -| curatedRuleSetCategories.list | v1alpha | chronicle.rule_set.list_curated_rule_set_categories | secops curated-rule rule-set-category list | -| curatedRules.get | v1alpha | chronicle.rule_set.get_curated_rule
chronicle.rule_set.get_curated_rule_by_name | secops curated-rule rule get | -| curatedRules.list | v1alpha | chronicle.rule_set.list_curated_rules | secops curated-rule rule list | -| dashboardCharts.batchGet |v1alpha| | | -|dashboardCharts.get |v1alpha|chronicle.dashboard.get_chart |secops dashboard get-chart | -|dashboardQueries.execute |v1alpha|chronicle.dashboard_query.execute_query |secops dashboard-query execute | -|dashboardQueries.get |v1alpha|chronicle.dashboard_query.get_execute_query |secops dashboard-query get | -|dashboards.copy |v1alpha| | | -|dashboards.create |v1alpha| | | -|dashboards.delete |v1alpha| | | -|dashboards.get |v1alpha| | | -|dashboards.list |v1alpha| | | -|dataAccessLabels.create |v1alpha| | | -|dataAccessLabels.delete |v1alpha| | | -|dataAccessLabels.get |v1alpha| | | -|dataAccessLabels.list |v1alpha| | | -|dataAccessLabels.patch |v1alpha| | | -|dataAccessScopes.create |v1alpha| | | -|dataAccessScopes.delete |v1alpha| | | -|dataAccessScopes.get |v1alpha| | | -|dataAccessScopes.list |v1alpha| | | -|dataAccessScopes.patch |v1alpha| | | -|dataExports.cancel |v1alpha|chronicle.data_export.cancel_data_export |secops export cancel | -|dataExports.create |v1alpha|chronicle.data_export.create_data_export |secops export create | -|dataExports.fetchavailablelogtypes |v1alpha|chronicle.data_export.fetch_available_log_types |secops export log-types | -|dataExports.get |v1alpha|chronicle.data_export.get_data_export |secops export status | -|dataExports.list |v1alpha|chronicle.data_export.list_data_export |secops export list | -|dataExports.patch |v1alpha|chronicle.data_export.update_data_export |secops export update | -|dataTableOperationErrors.get |v1alpha| | | -|dataTables.create |v1alpha|chronicle.data_table.create_data_table |secops data-table create | -|dataTables.dataTableRows.bulkCreate |v1alpha|chronicle.data_table.create_data_table_rows |secops data-table add-rows | -|dataTables.dataTableRows.bulkCreateAsync |v1alpha| | | -|dataTables.dataTableRows.bulkGet |v1alpha| | | -|dataTables.dataTableRows.bulkReplace |v1alpha|chronicle.data_table.replace_data_table_rows |secops data-table replace-rows | -|dataTables.dataTableRows.bulkReplaceAsync |v1alpha| | | -|dataTables.dataTableRows.bulkUpdate |v1alpha|chronicle.data_table.update_data_table_rows |secops data-table update-rows | -|dataTables.dataTableRows.bulkUpdateAsync |v1alpha| | | -|dataTables.dataTableRows.create |v1alpha| | | -|dataTables.dataTableRows.delete |v1alpha|chronicle.data_table.delete_data_table_rows |secops data-table delete-rows | -|dataTables.dataTableRows.get |v1alpha| | | -|dataTables.dataTableRows.list |v1alpha|chronicle.data_table.list_data_table_rows |secops data-table list-rows | -|dataTables.dataTableRows.patch |v1alpha| | | -|dataTables.delete |v1alpha|chronicle.data_table.delete_data_table |secops data-table delete | -|dataTables.get |v1alpha|chronicle.data_table.get_data_table |secops data-table get | -|dataTables.list |v1alpha|chronicle.data_table.list_data_tables |secops data-table list | -|dataTables.patch |v1alpha| | | -|dataTables.upload |v1alpha| | | -|dataTaps.create |v1alpha| | | -|dataTaps.delete |v1alpha| | | -|dataTaps.get |v1alpha| | | -|dataTaps.list |v1alpha| | | -|dataTaps.patch |v1alpha| | | -|delete |v1alpha| | | -|enrichmentControls.create |v1alpha| | | -|enrichmentControls.delete |v1alpha| | | -|enrichmentControls.get |v1alpha| | | -|enrichmentControls.list |v1alpha| | | -|entities.get |v1alpha| | | -|entities.import |v1alpha|chronicle.log_ingest.import_entities |secops entity import | -|entities.modifyEntityRiskScore |v1alpha| | | -|entities.queryEntityRiskScoreModifications |v1alpha| | | -|entityRiskScores.query |v1alpha| | | -|errorNotificationConfigs.create |v1alpha| | | -|errorNotificationConfigs.delete |v1alpha| | | -|errorNotificationConfigs.get |v1alpha| | | -|errorNotificationConfigs.list |v1alpha| | | -|errorNotificationConfigs.patch |v1alpha| | | -|events.batchGet |v1alpha| | | -|events.get |v1alpha| | | -|events.import |v1alpha|chronicle.log_ingest.ingest_udm |secops log ingest-udm | -|extractSyslog |v1alpha| | | -|federationGroups.create |v1alpha| | | -|federationGroups.delete |v1alpha| | | -|federationGroups.get |v1alpha| | | -|federationGroups.list |v1alpha| | | -|federationGroups.patch |v1alpha| | | -|feedPacks.get |v1alpha| | | -|feedPacks.list |v1alpha| | | -|feedServiceAccounts.fetchServiceAccountForCustomer |v1alpha| | | -|feedSourceTypeSchemas.list |v1alpha| | | -|feedSourceTypeSchemas.logTypeSchemas.list |v1alpha| | | -|feeds.create |v1alpha|chronicle.feeds.create_feed |secops feed create | -|feeds.delete |v1alpha|chronicle.feeds.delete_feed |secops feed delete | -|feeds.disable |v1alpha|chronicle.feeds.disable_feed |secops feed disable | -|feeds.enable |v1alpha|chronicle.feeds.enable_feed |secops feed enable | -|feeds.generateSecret |v1alpha|chronicle.feeds.generate_secret |secops feed secret | -|feeds.get |v1alpha|chronicle.feeds.get_feed |secops feed get | -|feeds.importPushLogs |v1alpha| | | -|feeds.list |v1alpha|chronicle.feeds.list_feeds |secops feed list | -|feeds.patch |v1alpha|chronicle.feeds.update_feed |secops feed update | -|feeds.scheduleTransfer |v1alpha| | | -|fetchFederationAccess |v1alpha| | | -|findEntity |v1alpha| | | -|findEntityAlerts |v1alpha| | | -|findRelatedEntities |v1alpha| | | -|findUdmFieldValues |v1alpha| | | -|findingsGraph.exploreNode |v1alpha| | | -|findingsGraph.initializeGraph |v1alpha| | | -|findingsRefinements.computeFindingsRefinementActivity |v1alpha|chronicle.rule_exclusion.compute_rule_exclusion_activity |secops rule-exclusion compute-activity | -|findingsRefinements.create |v1alpha|chronicle.rule_exclusion.create_rule_exclusion |secops rule-exclusion create | -|findingsRefinements.get |v1alpha|chronicle.rule_exclusion.get_rule_exclusion |secops rule-exclusion get | -|findingsRefinements.getDeployment |v1alpha|chronicle.rule_exclusion.get_rule_exclusion_deployment |secops rule-exclusion get-deployment | -|findingsRefinements.list |v1alpha|chronicle.rule_exclusion.list_rule_exclusions |secops rule-exclusion list | -|findingsRefinements.patch |v1alpha|chronicle.rule_exclusion.patch_rule_exclusion |secops rule-exclusion update | -|findingsRefinements.updateDeployment |v1alpha|chronicle.rule_exclusion.update_rule_exclusion_deployment |secops rule-exclusion update-deployment| -|forwarders.collectors.create |v1alpha| | | -|forwarders.collectors.delete |v1alpha| | | -|forwarders.collectors.get |v1alpha| | | -|forwarders.collectors.list |v1alpha| | | -|forwarders.collectors.patch |v1alpha| | | -|forwarders.create |v1alpha|chronicle.log_ingest.create_forwarder |secops forwarder create | -|forwarders.delete |v1alpha|chronicle.log_ingest.delete_forwarder |secops forwarder delete | -|forwarders.generateForwarderFiles |v1alpha| | | -|forwarders.get |v1alpha|chronicle.log_ingest.get_forwarder |secops forwarder get | -|forwarders.importStatsEvents |v1alpha| | | -|forwarders.list |v1alpha|chronicle.log_ingest.list_forwarder |secops forwarder list | -|forwarders.patch |v1alpha|chronicle.log_ingest.update_forwarder |secops forwarder update | -|generateCollectionAgentAuth |v1alpha| | | -|generateSoarAuthJwt |v1alpha| | | -|generateUdmKeyValueMappings |v1alpha| | | -|generateWorkspaceConnectionToken |v1alpha| | | -|get |v1alpha| | | -|getBigQueryExport |v1alpha| | | -|getMultitenantDirectory |v1alpha| | | -|getRiskConfig |v1alpha| | | -|ingestionLogLabels.get |v1alpha| | | -|ingestionLogLabels.list |v1alpha| | | -|ingestionLogNamespaces.get |v1alpha| | | -|ingestionLogNamespaces.list |v1alpha| | | -|investigations.fetchAssociated |v1alpha|chronicle.investigations.fetch_associated_investigations |secops investigation fetch-associated | -|investigations.get |v1alpha|chronicle.investigations.get_investigation |secops investigation get | -|investigations.list |v1alpha|chronicle.investigations.list_investigations |secops investigation list | -|investigations.trigger |v1alpha|chronicle.investigations.trigger_investigation |secops investigation trigger | -|iocs.batchGet |v1alpha| | | -|iocs.findFirstAndLastSeen |v1alpha| | | -|iocs.get |v1alpha| | | -|iocs.getIocState |v1alpha| | | -|iocs.searchCuratedDetectionsForIoc |v1alpha| | | -|iocs.updateIocState |v1alpha| | | -|legacy.legacyBatchGetCases |v1alpha|chronicle.case.get_cases_from_list |secops case | -|legacy.legacyBatchGetCollections |v1alpha| | | -|legacy.legacyCreateOrUpdateCase |v1alpha| | | -|legacy.legacyCreateSoarAlert |v1alpha| | | -|legacy.legacyFetchAlertsView |v1alpha|chronicle.alert.get_alerts |secops alert | -|legacy.legacyFetchUdmSearchCsv |v1alpha|chronicle.udm_search.fetch_udm_search_csv |secops search --csv | -|legacy.legacyFetchUdmSearchView |v1alpha|chronicle.udm_search.fetch_udm_search_view |secops udm-search-view | -|legacy.legacyFindAssetEvents |v1alpha| | | -|legacy.legacyFindRawLogs |v1alpha| | | -|legacy.legacyFindUdmEvents |v1alpha| | | -|legacy.legacyGetAlert |v1alpha|chronicle.rule_alert.get_alert | | -|legacy.legacyGetCuratedRulesTrends |v1alpha| | | -|legacy.legacyGetDetection |v1alpha| | | -|legacy.legacyGetEventForDetection |v1alpha| | | -|legacy.legacyGetRuleCounts |v1alpha| | | -|legacy.legacyGetRulesTrends |v1alpha| | | -|legacy.legacyListCases |v1alpha|chronicle.case.get_cases |secops case --ids | -|legacy.legacyRunTestRule |v1alpha|chronicle.rule.run_rule_test |secops rule validate | -|legacy.legacySearchArtifactEvents |v1alpha| | | -|legacy.legacySearchArtifactIoCDetails |v1alpha| | | -|legacy.legacySearchAssetEvents |v1alpha| | | -|legacy.legacySearchCuratedDetections |v1alpha| | | -|legacy.legacySearchCustomerStats |v1alpha| | | -|legacy.legacySearchDetections |v1alpha|chronicle.rule_detection.list_detections | | -|legacy.legacySearchDomainsRecentlyRegistered |v1alpha| | | -|legacy.legacySearchDomainsTimingStats |v1alpha| | | -|legacy.legacySearchEnterpriseWideAlerts |v1alpha| | | -|legacy.legacySearchEnterpriseWideIoCs |v1alpha|chronicle.ioc.list_iocs |secops iocs | -|legacy.legacySearchFindings |v1alpha| | | -|legacy.legacySearchIngestionStats |v1alpha| | | -|legacy.legacySearchIoCInsights |v1alpha| | | -|legacy.legacySearchRawLogs |v1alpha| | | -|legacy.legacySearchRuleDetectionCountBuckets |v1alpha| | | -|legacy.legacySearchRuleDetectionEvents |v1alpha| | | -|legacy.legacySearchRuleResults |v1alpha| | | -|legacy.legacySearchRulesAlerts |v1alpha|chronicle.rule_alert.search_rule_alerts | | -|legacy.legacySearchUserEvents |v1alpha| | | -|legacy.legacyStreamDetectionAlerts |v1alpha| | | -|legacy.legacyTestRuleStreaming |v1alpha| | | -|legacy.legacyUpdateAlert |v1alpha|chronicle.rule_alert.update_alert | | -|listAllFindingsRefinementDeployments |v1alpha| | | -|logTypes.create |v1alpha| | | -|logTypes.generateEventTypesSuggestions |v1alpha| | | -|logTypes.get |v1alpha| | | -|logTypes.getLogTypeSetting |v1alpha| | | -|logTypes.legacySubmitParserExtension |v1alpha| | | -|logTypes.list |v1alpha| | | -|logTypes.logs.export |v1alpha| | | -|logTypes.logs.get |v1alpha| | | -|logTypes.logs.import |v1alpha|chronicle.log_ingest.ingest_log |secops log ingest | -|logTypes.logs.list |v1alpha| | | -|logTypes.parserExtensions.activate |v1alpha|chronicle.parser_extension.activate_parser_extension |secops parser-extension activate | -|logTypes.parserExtensions.create |v1alpha|chronicle.parser_extension.create_parser_extension |secops parser-extension create | -|logTypes.parserExtensions.delete |v1alpha|chronicle.parser_extension.delete_parser_extension |secops parser-extension delete | -|logTypes.parserExtensions.extensionValidationReports.get |v1alpha| | | -|logTypes.parserExtensions.extensionValidationReports.list |v1alpha| | | -|logTypes.parserExtensions.extensionValidationReports.validationErrors.list |v1alpha| | | -|logTypes.parserExtensions.get |v1alpha|chronicle.parser_extension.get_parser_extension |secops parser-extension get | -|logTypes.parserExtensions.list |v1alpha|chronicle.parser_extension.list_parser_extensions |secops parser-extension list | -|logTypes.parserExtensions.validationReports.get |v1alpha| | | -|logTypes.parserExtensions.validationReports.parsingErrors.list |v1alpha| | | -|logTypes.parsers.activate |v1alpha|chronicle.parser.activate_parser |secops parser activate | -|logTypes.parsers.activateReleaseCandidateParser |v1alpha|chronicle.parser.activate_release_candidate |secops parser activate-rc | -|logTypes.parsers.copy |v1alpha|chronicle.parser.copy_parser |secops parser copy | -|logTypes.parsers.create |v1alpha|chronicle.parser.create_parser |secops parser create | -|logTypes.parsers.deactivate |v1alpha|chronicle.parser.deactivate_parser |secops parser deactivate | -|logTypes.parsers.delete |v1alpha|chronicle.parser.delete_parser |secops parser delete | -|logTypes.parsers.get |v1alpha|chronicle.parser.get_parser |secops parser get | -|logTypes.parsers.list |v1alpha|chronicle.parser.list_parsers |secops parser list | -|logTypes.parsers.validationReports.get |v1alpha| | | -|logTypes.parsers.validationReports.parsingErrors.list |v1alpha| | | -|logTypes.patch |v1alpha| | | -|logTypes.runParser |v1alpha|chronicle.parser.run_parser |secops parser run | -|logTypes.updateLogTypeSetting |v1alpha| | | -|logProcessingPipelines.associateStreams |v1alpha|chronicle.log_processing_pipelines.associate_streams |secops log-processing associate-streams| -|logProcessingPipelines.create |v1alpha|chronicle.log_processing_pipelines.create_log_processing_pipeline|secops log-processing create | -|logProcessingPipelines.delete |v1alpha|chronicle.log_processing_pipelines.delete_log_processing_pipeline|secops log-processing delete | -|logProcessingPipelines.dissociateStreams |v1alpha|chronicle.log_processing_pipelines.dissociate_streams |secops log-processing dissociate-streams| -|logProcessingPipelines.fetchAssociatedPipeline |v1alpha|chronicle.log_processing_pipelines.fetch_associated_pipeline|secops log-processing fetch-associated | -|logProcessingPipelines.fetchSampleLogsByStreams |v1alpha|chronicle.log_processing_pipelines.fetch_sample_logs_by_streams|secops log-processing fetch-sample-logs| -|logProcessingPipelines.get |v1alpha|chronicle.log_processing_pipelines.get_log_processing_pipeline|secops log-processing get | -|logProcessingPipelines.list |v1alpha|chronicle.log_processing_pipelines.list_log_processing_pipelines|secops log-processing list | -|logProcessingPipelines.patch |v1alpha|chronicle.log_processing_pipelines.update_log_processing_pipeline|secops log-processing update | -|logProcessingPipelines.testPipeline |v1alpha|chronicle.log_processing_pipelines.test_pipeline |secops log-processing test | -|logs.classify |v1alpha|chronicle.log_types.classify_logs |secops log classify | -| nativeDashboards.addChart | v1alpha |chronicle.dashboard.add_chart |secops dashboard add-chart | -| nativeDashboards.create | v1alpha |chronicle.dashboard.create_dashboard |secops dashboard create | -| nativeDashboards.delete | v1alpha |chronicle.dashboard.delete_dashboard |secops dashboard delete | -| nativeDashboards.duplicate | v1alpha |chronicle.dashboard.duplicate_dashboard |secops dashboard duplicate | -| nativeDashboards.duplicateChart | v1alpha | | | -| nativeDashboards.editChart | v1alpha |chronicle.dashboard.edit_chart |secops dashboard edit-chart | -| nativeDashboards.export | v1alpha |chronicle.dashboard.export_dashboard |secops dashboard export | -| nativeDashboards.get | v1alpha |chronicle.dashboard.get_dashboard |secops dashboard get | -| nativeDashboards.import | v1alpha |chronicle.dashboard.import_dashboard |secops dashboard import | -| nativeDashboards.list | v1alpha |chronicle.dashboard.list_dashboards |secops dashboard list | -| nativeDashboards.patch | v1alpha |chronicle.dashboard.update_dashboard |secops dashboard update | -| nativeDashboards.removeChart | v1alpha |chronicle.dashboard.remove_chart |secops dashboard remove-chart | -|operations.cancel |v1alpha| | | -|operations.delete |v1alpha| | | -|operations.get |v1alpha| | | -|operations.list |v1alpha| | | -|operations.streamSearch |v1alpha| | | -|queryProductSourceStats |v1alpha| | | -|referenceLists.create |v1alpha| | | -|referenceLists.get |v1alpha| | | -|referenceLists.list |v1alpha| | | -|referenceLists.patch |v1alpha| | | -|report |v1alpha| | | -|ruleExecutionErrors.list |v1alpha|chronicle.rule_detection.list_errors | | -|rules.create |v1alpha| | | -|rules.delete |v1alpha| | | -|rules.deployments.list |v1alpha| | | -|rules.get |v1alpha| | | -|rules.getDeployment |v1alpha| | | -|rules.list |v1alpha| | | -|rules.listRevisions |v1alpha| | | -|rules.patch |v1alpha| | | -|rules.retrohunts.create |v1alpha| | | -|rules.retrohunts.get |v1alpha| | | -|rules.retrohunts.list |v1alpha| | | -|rules.updateDeployment |v1alpha| | | -|searchEntities |v1alpha| | | -|searchRawLogs |v1alpha|chronicle.log_search.search_raw_logs |secops search raw-logs | -|summarizeEntitiesFromQuery |v1alpha|chronicle.entity.summarize_entity |secops entity | -|summarizeEntity |v1alpha|chronicle.entity.summarize_entity | | -|testFindingsRefinement |v1alpha| | | -|translateUdmQuery |v1alpha|chronicle.nl_search.translate_nl_to_udm | | -|translateYlRule |v1alpha| | | -|udmSearch |v1alpha|chronicle.search.search_udm |secops search | -|undelete |v1alpha| | | -|updateBigQueryExport |v1alpha| | | -|updateRiskConfig |v1alpha| | | -|users.clearConversationHistory |v1alpha| | | -|users.conversations.create |v1alpha|chronicle.gemini.create_conversation | | -|users.conversations.delete |v1alpha| | | -|users.conversations.get |v1alpha| | | -|users.conversations.list |v1alpha| | | -|users.conversations.messages.create |v1alpha|chronicle.gemini.query_gemini |secops gemini | -|users.conversations.messages.delete |v1alpha| | | -|users.conversations.messages.get |v1alpha| | | -|users.conversations.messages.list |v1alpha| | | -|users.conversations.messages.patch |v1alpha| | | -|users.conversations.patch |v1alpha| | | -|users.getPreferenceSet |v1alpha|chronicle.gemini.opt_in_to_gemini |secops gemini --opt-in | -|users.searchQueries.create |v1alpha| | | -|users.searchQueries.delete |v1alpha| | | -|users.searchQueries.get |v1alpha| | | -|users.searchQueries.list |v1alpha| | | -|users.searchQueries.patch |v1alpha| | | -|users.updatePreferenceSet |v1alpha| | | -|validateQuery |v1alpha|chronicle.validate.validate_query | | -|verifyReferenceList |v1alpha| | | -|verifyRuleText |v1alpha|chronicle.rule_validation.validate_rule |secops rule validate | -|watchlists.create |v1alpha| | | -|watchlists.delete |v1alpha| | | -|watchlists.entities.add |v1alpha| | | -|watchlists.entities.batchAdd |v1alpha| | | -|watchlists.entities.batchRemove |v1alpha| | | -|watchlists.entities.remove |v1alpha| | | -|watchlists.get |v1alpha| | | -|watchlists.list |v1alpha| | | -|watchlists.listEntities |v1alpha| | | -|watchlists.patch |v1alpha| | | +| REST Resource | Version | secops-wrapper module | CLI Command | +|----------------------------------------------------------------------------------|-----------|------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| dataAccessLabels.create | v1 | | | +| dataAccessLabels.delete | v1 | | | +| dataAccessLabels.get | v1 | | | +| dataAccessLabels.list | v1 | | | +| dataAccessLabels.patch | v1 | | | +| dataAccessScopes.create | v1 | | | +| dataAccessScopes.delete | v1 | | | +| dataAccessScopes.get | v1 | | | +| dataAccessScopes.list | v1 | | | +| dataAccessScopes.patch | v1 | | | +| get | v1 | | | +| operations.cancel | v1 | | | +| operations.delete | v1 | | | +| operations.get | v1 | | | +| operations.list | v1 | | | +| referenceLists.create | v1 | chronicle.reference_list.create_reference_list | secops reference-list create | +| referenceLists.get | v1 | chronicle.reference_list.get_reference_list | secops reference-list get | +| referenceLists.list | v1 | chronicle.reference_list.list_reference_lists | secops reference-list list | +| referenceLists.patch | v1 | chronicle.reference_list.update_reference_list | secops reference-list update | +| rules.create | v1 | chronicle.rule.create_rule | secops rule create | +| rules.delete | v1 | chronicle.rule.delete_rule | secops rule delete | +| rules.deployments.list | v1 | | | +| rules.get | v1 | chronicle.rule.get_rule | secops rule get | +| rules.getDeployment | v1 | | | +| rules.list | v1 | chronicle.rule.list_rules | secops rule list | +| rules.listRevisions | v1 | | | +| rules.patch | v1 | chronicle.rule.update_rule | secops rule update | +| rules.retrohunts.create | v1 | chronicle.rule_retrohunt.create_retrohunt | secops rule-retrohunt create | +| rules.retrohunts.get | v1 | chronicle.rule_retrohunt.get_retrohunt | secops rule-retrohunt get | +| rules.retrohunts.list | v1 | chronicle.rule_retrohunt.list_retrohunts | secops rule-retrohunt list | +| rules.updateDeployment | v1 | chronicle.rule.enable_rule | secops rule enable | +| watchlists.create | v1 | chronicle.watchlist.create_watchlist | secops watchlist create | +| watchlists.delete | v1 | chronicle.watchlist.delete_watchlist | secops watchlist delete | +| watchlists.get | v1 | chronicle.watchlist.get_watchlist | secops watchlist get | +| watchlists.list | v1 | chronicle.watchlist.list_watchlists | secops watchlist list | +| watchlists.patch | v1 | chronicle.watchlist.update_watchlist | secops watchlist update | +| dataAccessLabels.create | v1beta | | | +| dataAccessLabels.delete | v1beta | | | +| dataAccessLabels.get | v1beta | | | +| dataAccessLabels.list | v1beta | | | +| dataAccessLabels.patch | v1beta | | | +| dataAccessScopes.create | v1beta | | | +| dataAccessScopes.delete | v1beta | | | +| dataAccessScopes.get | v1beta | | | +| dataAccessScopes.list | v1beta | | | +| dataAccessScopes.patch | v1beta | | | +| get | v1beta | | | +| integrations.create | v1beta | | | +| integrations.delete | v1beta | chronicle.soar.integration.integrations.delete_integration | secops integration integrations delete | +| integrations.download | v1beta | chronicle.soar.integration.integrations.download_integration | secops integration integrations download | +| integrations.downloadDependency | v1beta | chronicle.soar.integration.integrations.download_integration_dependency | secops integration integrations download-dependency | +| integrations.exportIntegrationItems | v1beta | chronicle.soar.integration.integrations.export_integration_items | secops integration integrations export-items | +| integrations.fetchAffectedItems | v1beta | chronicle.soar.integration.integrations.get_integration_affected_items | secops integration integrations get-affected-items | +| integrations.fetchAgentIntegrations | v1beta | chronicle.soar.integration.integrations.get_agent_integrations | secops integration integrations get-agent | +| integrations.fetchCommercialDiff | v1beta | chronicle.soar.integration.integrations.get_integration_diff | secops integration integrations get-diff | +| integrations.fetchDependencies | v1beta | chronicle.soar.integration.integrations.get_integration_dependencies | secops integration integrations get-dependencies | +| integrations.fetchRestrictedAgents | v1beta | chronicle.soar.integration.integrations.get_integration_restricted_agents | secops integration integrations get-restricted-agents | +| integrations.get | v1beta | chronicle.soar.integration.integrations.get_integration | secops integration integrations get | +| integrations.getFetchProductionDiff | v1beta | chronicle.soar.integration.integrations.get_integration_diff(diff_type=DiffType.PRODUCTION) | secops integration integrations get-diff | +| integrations.getFetchStagingDiff | v1beta | chronicle.soar.integration.integrations.get_integration_diff(diff_type=DiffType.STAGING) | secops integration integrations get-diff | +| integrations.import | v1beta | | | +| integrations.importIntegrationDependency | v1beta | | | +| integrations.importIntegrationItems | v1beta | | | +| integrations.list | v1beta | chronicle.soar.integration.integrations.list_integrations | secops integration integrations list | +| integrations.patch | v1beta | | | +| integrations.pushToProduction | v1beta | chronicle.soar.integration.integrations.transition_integration(target_mode=TargetMode.PRODUCTION) | secops integration integrations transition | +| integrations.pushToStaging | v1beta | chronicle.soar.integration.integrations.transition_integration(target_mode=TargetMode.STAGING) | secops integration integrations transition | +| integrations.updateCustomIntegration | v1beta | | | +| integrations.upload | v1beta | | | +| integrations.actions.create | v1beta | chronicle.soar.integration.actions.create_integration_action | secops integration actions create | +| integrations.actions.delete | v1beta | chronicle.soar.integration.actions.delete_integration_action | secops integration actions delete | +| integrations.actions.executeTest | v1beta | chronicle.soar.integration.actions.execute_integration_action_test | secops integration actions test | +| integrations.actions.fetchActionsByEnvironment | v1beta | chronicle.soar.integration.actions.get_integration_actions_by_environment | | +| integrations.actions.fetchTemplate | v1beta | chronicle.soar.integration.actions.get_integration_action_template | secops integration actions template | +| integrations.actions.get | v1beta | chronicle.soar.integration.actions.get_integration_action | secops integration actions get | +| integrations.actions.list | v1beta | chronicle.soar.integration.actions.list_integration_actions | secops integration actions list | +| integrations.actions.patch | v1beta | chronicle.soar.integration.actions.update_integration_action | secops integration actions update | +| integrations.actions.revisions.create | v1beta | chronicle.soar.integration.action_revisions.create_integration_action_revision | secops integration action-revisions create | +| integrations.actions.revisions.delete | v1beta | chronicle.soar.integration.action_revisions.delete_integration_action_revision | secops integration action-revisions delete | +| integrations.actions.revisions.list | v1beta | chronicle.soar.integration.action_revisions.list_integration_action_revisions | secops integration action-revisions list | +| integrations.actions.revisions.rollback | v1beta | chronicle.soar.integration.action_revisions.rollback_integration_action_revision | secops integration action-revisions rollback | +| integrations.connectors.create | v1beta | chronicle.soar.integration.connectors.create_integration_connector | secops integration connectors create | +| integrations.connectors.delete | v1beta | chronicle.soar.integration.connectors.delete_integration_connector | secops integration connectors delete | +| integrations.connectors.executeTest | v1beta | chronicle.soar.integration.connectors.execute_integration_connector_test | secops integration connectors test | +| integrations.connectors.fetchTemplate | v1beta | chronicle.soar.integration.connectors.get_integration_connector_template | secops integration connectors template | +| integrations.connectors.get | v1beta | chronicle.soar.integration.connectors.get_integration_connector | secops integration connectors get | +| integrations.connectors.list | v1beta | chronicle.soar.integration.connectors.list_integration_connectors | secops integration connectors list | +| integrations.connectors.patch | v1beta | chronicle.soar.integration.connectors.update_integration_connector | secops integration connectors update | +| integrations.connectors.revisions.create | v1beta | chronicle.soar.integration.connector_revisions.create_integration_connector_revision | secops integration connector-revisions create | +| integrations.connectors.revisions.delete | v1beta | chronicle.soar.integration.connector_revisions.delete_integration_connector_revision | secops integration connector-revisions delete | +| integrations.connectors.revisions.list | v1beta | chronicle.soar.integration.connector_revisions.list_integration_connector_revisions | secops integration connector-revisions list | +| integrations.connectors.revisions.rollback | v1beta | chronicle.soar.integration.connector_revisions.rollback_integration_connector_revision | secops integration connector-revisions rollback | +| integrations.connectors.contextProperties.clearAll | v1beta | chronicle.soar.integration.connector_context_properties.delete_all_connector_context_properties | secops integration connector-context-properties delete-all | +| integrations.connectors.contextProperties.create | v1beta | chronicle.soar.integration.connector_context_properties.create_connector_context_property | secops integration connector-context-properties create | +| integrations.connectors.contextProperties.delete | v1beta | chronicle.soar.integration.connector_context_properties.delete_connector_context_property | secops integration connector-context-properties delete | +| integrations.connectors.contextProperties.get | v1beta | chronicle.soar.integration.connector_context_properties.get_connector_context_property | secops integration connector-context-properties get | +| integrations.connectors.contextProperties.list | v1beta | chronicle.soar.integration.connector_context_properties.list_connector_context_properties | secops integration connector-context-properties list | +| integrations.connectors.contextProperties.patch | v1beta | chronicle.soar.integration.connector_context_properties.update_connector_context_property | secops integration connector-context-properties update | +| integrations.connectors.connectorInstances.logs.get | v1beta | chronicle.soar.integration.connector_instance_logs.get_connector_instance_log | secops integration connector-instance-logs get | +| integrations.connectors.connectorInstances.logs.list | v1beta | chronicle.soar.integration.connector_instance_logs.list_connector_instance_logs | secops integration connector-instance-logs list | +| integrations.connectors.connectorInstances.create | v1beta | chronicle.soar.integration.connector_instances.create_connector_instance | secops integration connector-instances create | +| integrations.connectors.connectorInstances.delete | v1beta | chronicle.soar.integration.connector_instances.delete_connector_instance | secops integration connector-instances delete | +| integrations.connectors.connectorInstances.fetchLatestDefinition | v1beta | chronicle.soar.integration.connector_instances.get_connector_instance_latest_definition | secops integration connector-instances get-latest-definition | +| integrations.connectors.connectorInstances.get | v1beta | chronicle.soar.integration.connector_instances.get_connector_instance | secops integration connector-instances get | +| integrations.connectors.connectorInstances.list | v1beta | chronicle.soar.integration.connector_instances.list_connector_instances | secops integration connector-instances list | +| integrations.connectors.connectorInstances.patch | v1beta | chronicle.soar.integration.connector_instances.update_connector_instance | secops integration connector-instances update | +| integrations.connectors.connectorInstances.runOnDemand | v1beta | chronicle.soar.integration.connector_instances.run_connector_instance_on_demand | secops integration connector-instances run-on-demand | +| integrations.connectors.connectorInstances.setLogsCollection | v1beta | chronicle.soar.integration.connector_instances.set_connector_instance_logs_collection | secops integration connector-instances set-logs-collection | +| integrations.integrationInstances.create | v1beta | chronicle.soar.integration.integration_instances.create_integration_instance | secops integration instances create | +| integrations.integrationInstances.delete | v1beta | chronicle.soar.integration.integration_instances.delete_integration_instance | secops integration instances delete | +| integrations.integrationInstances.executeTest | v1beta | chronicle.soar.integration.integration_instances.execute_integration_instance_test | secops integration instances test | +| integrations.integrationInstances.fetchAffectedItems | v1beta | chronicle.soar.integration.integration_instances.get_integration_instance_affected_items | secops integration instances get-affected-items | +| integrations.integrationInstances.fetchDefaultInstance | v1beta | chronicle.soar.integration.integration_instances.get_default_integration_instance | secops integration instances get-default | +| integrations.integrationInstances.get | v1beta | chronicle.soar.integration.integration_instances.get_integration_instance | secops integration instances get | +| integrations.integrationInstances.list | v1beta | chronicle.soar.integration.integration_instances.list_integration_instances | secops integration instances list | +| integrations.integrationInstances.patch | v1beta | chronicle.soar.integration.integration_instances.update_integration_instance | secops integration instances update | +| integrations.jobs.create | v1beta | chronicle.soar.integration.jobs.create_integration_job | secops integration jobs create | +| integrations.jobs.delete | v1beta | chronicle.soar.integration.jobs.delete_integration_job | secops integration jobs delete | +| integrations.jobs.executeTest | v1beta | chronicle.soar.integration.jobs.execute_integration_job_test | secops integration jobs test | +| integrations.jobs.fetchTemplate | v1beta | chronicle.soar.integration.jobs.get_integration_job_template | secops integration jobs template | +| integrations.jobs.get | v1beta | chronicle.soar.integration.jobs.get_integration_job | secops integration jobs get | +| integrations.jobs.list | v1beta | chronicle.soar.integration.jobs.list_integration_jobs | secops integration jobs list | +| integrations.jobs.patch | v1beta | chronicle.soar.integration.jobs.update_integration_job | secops integration jobs update | +| integrations.managers.create | v1beta | chronicle.soar.integration.managers.create_integration_manager | secops integration managers create | +| integrations.managers.delete | v1beta | chronicle.soar.integration.managers.delete_integration_manager | secops integration managers delete | +| integrations.managers.fetchTemplate | v1beta | chronicle.soar.integration.managers.get_integration_manager_template | secops integration managers template | +| integrations.managers.get | v1beta | chronicle.soar.integration.managers.get_integration_manager | secops integration managers get | +| integrations.managers.list | v1beta | chronicle.soar.integration.managers.list_integration_managers | secops integration managers list | +| integrations.managers.patch | v1beta | chronicle.soar.integration.managers.update_integration_manager | secops integration managers update | +| integrations.managers.revisions.create | v1beta | chronicle.soar.integration.manager_revisions.create_integration_manager_revision | secops integration manager-revisions create | +| integrations.managers.revisions.delete | v1beta | chronicle.soar.integration.manager_revisions.delete_integration_manager_revision | secops integration manager-revisions delete | +| integrations.managers.revisions.get | v1beta | chronicle.soar.integration.manager_revisions.get_integration_manager_revision | secops integration manager-revisions get | +| integrations.managers.revisions.list | v1beta | chronicle.soar.integration.manager_revisions.list_integration_manager_revisions | secops integration manager-revisions list | +| integrations.managers.revisions.rollback | v1beta | chronicle.soar.integration.manager_revisions.rollback_integration_manager_revision | secops integration manager-revisions rollback | +| integrations.jobs.revisions.create | v1beta | chronicle.soar.integration.job_revisions.create_integration_job_revision | secops integration job-revisions create | +| integrations.jobs.revisions.delete | v1beta | chronicle.soar.integration.job_revisions.delete_integration_job_revision | secops integration job-revisions delete | +| integrations.jobs.revisions.list | v1beta | chronicle.soar.integration.job_revisions.list_integration_job_revisions | secops integration job-revisions list | +| integrations.jobs.revisions.rollback | v1beta | chronicle.soar.integration.job_revisions.rollback_integration_job_revision | secops integration job-revisions rollback | +| integrations.jobs.jobInstances.create | v1beta | chronicle.soar.integration.job_instances.create_integration_job_instance | secops integration job-instances create | +| integrations.jobs.jobInstances.delete | v1beta | chronicle.soar.integration.job_instances.delete_integration_job_instance | secops integration job-instances delete | +| integrations.jobs.jobInstances.get | v1beta | chronicle.soar.integration.job_instances.get_integration_job_instance | secops integration job-instances get | +| integrations.jobs.jobInstances.list | v1beta | chronicle.soar.integration.job_instances.list_integration_job_instances | secops integration job-instances list | +| integrations.jobs.jobInstances.patch | v1beta | chronicle.soar.integration.job_instances.update_integration_job_instance | secops integration job-instances update | +| integrations.jobs.jobInstances.runOnDemand | v1beta | chronicle.soar.integration.job_instances.run_integration_job_instance_on_demand | secops integration job-instances run-on-demand | +| integrations.jobs.contextProperties.clearAll | v1beta | chronicle.soar.integration.job_context_properties.delete_all_job_context_properties | secops integration job-context-properties delete-all | +| integrations.jobs.contextProperties.create | v1beta | chronicle.soar.integration.job_context_properties.create_job_context_property | secops integration job-context-properties create | +| integrations.jobs.contextProperties.delete | v1beta | chronicle.soar.integration.job_context_properties.delete_job_context_property | secops integration job-context-properties delete | +| integrations.jobs.contextProperties.get | v1beta | chronicle.soar.integration.job_context_properties.get_job_context_property | secops integration job-context-properties get | +| integrations.jobs.contextProperties.list | v1beta | chronicle.soar.integration.job_context_properties.list_job_context_properties | secops integration job-context-properties list | +| integrations.jobs.contextProperties.patch | v1beta | chronicle.soar.integration.job_context_properties.update_job_context_property | secops integration job-context-properties update | +| integrations.jobs.jobInstances.logs.get | v1beta | chronicle.soar.integration.job_instance_logs.get_job_instance_log | secops integration job-instance-logs get | +| integrations.jobs.jobInstances.logs.list | v1beta | chronicle.soar.integration.job_instance_logs.list_job_instance_logs | secops integration job-instance-logs list | +| marketplaceIntegrations.get | v1beta | chronicle.marketplace_integrations.get_marketplace_integration | secops integration marketplace get | +| marketplaceIntegrations.getDiff | v1beta | chronicle.marketplace_integrations.get_marketplace_integration_diff | secops integration marketplace diff | +| marketplaceIntegrations.install | v1beta | chronicle.marketplace_integrations.install_marketplace_integration | secops integration marketplace install | +| marketplaceIntegrations.list | v1beta | chronicle.marketplace_integrations.list_marketplace_integrations | secops integration marketplace list | +| marketplaceIntegrations.uninstall | v1beta | chronicle.marketplace_integrations.uninstall_marketplace_integration | secops integration marketplace uninstall | +| operations.cancel | v1beta | | | +| operations.delete | v1beta | | | +| operations.get | v1beta | | | +| operations.list | v1beta | | | +| referenceLists.create | v1beta | | | +| referenceLists.get | v1beta | | | +| referenceLists.list | v1beta | | | +| referenceLists.patch | v1beta | | | +| rules.create | v1beta | | | +| rules.delete | v1beta | | | +| rules.deployments.list | v1beta | | | +| rules.get | v1beta | | | +| rules.getDeployment | v1beta | | | +| rules.list | v1beta | | | +| rules.listRevisions | v1beta | | | +| rules.patch | v1beta | | | +| rules.retrohunts.create | v1beta | | | +| rules.retrohunts.get | v1beta | | | +| rules.retrohunts.list | v1beta | | | +| rules.updateDeployment | v1beta | | | +| watchlists.create | v1beta | | | +| watchlists.delete | v1beta | | | +| watchlists.get | v1beta | | | +| watchlists.list | v1beta | | | +| watchlists.patch | v1beta | | | +| analytics.entities.analyticValues.list | v1alpha | | | +| analytics.list | v1alpha | | | +| batchValidateWatchlistEntities | v1alpha | | | +| bigQueryAccess.provide | v1alpha | | | +| bigQueryExport.provision | v1alpha | | | +| cases.countPriorities | v1alpha | | | +| contentHub.featuredContentRules.list | v1alpha | chronicle.featured_content_rules.list_featured_content_rules | secops featured-content-rules list | +| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.batchUpdate | v1alpha | chronicle.rule_set.batch_update_curated_rule_set_deployments | | +| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.patch | v1alpha | chronicle.rule_set.update_curated_rule_set_deployment | secops curated-rule rule-set-deployment update | +| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.list | v1alpha | chronicle.rule_set.list_curated_rule_set_deployments | secops curated-rule rule-set-deployment list | +| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.get | v1alpha | chronicle.rule_set.get_curated_rule_set_deployment
chronicle.rule_set.get_curated_rule_set_deployment_by_name | secops curated-rule rule-set-deployment get | +| curatedRuleSetCategories.curatedRuleSets.get | v1alpha | chronicle.rule_set.get_curated_rule_set | secops curated-rule rule-set get | +| curatedRuleSetCategories.curatedRuleSets.list | v1alpha | chronicle.rule_set.list_curated_rule_sets | secops curated-rule rule-set list | +| curatedRuleSetCategories.get | v1alpha | chronicle.rule_set.get_curated_rule_set_category | secops curated-rule rule-set-category get | +| curatedRuleSetCategories.list | v1alpha | chronicle.rule_set.list_curated_rule_set_categories | secops curated-rule rule-set-category list | +| curatedRules.get | v1alpha | chronicle.rule_set.get_curated_rule
chronicle.rule_set.get_curated_rule_by_name | secops curated-rule rule get | +| curatedRules.list | v1alpha | chronicle.rule_set.list_curated_rules | secops curated-rule rule list | +| dashboardCharts.batchGet | v1alpha | | | +| dashboardCharts.get | v1alpha | chronicle.dashboard.get_chart | secops dashboard get-chart | +| dashboardQueries.execute | v1alpha | chronicle.dashboard_query.execute_query | secops dashboard-query execute | +| dashboardQueries.get | v1alpha | chronicle.dashboard_query.get_execute_query | secops dashboard-query get | +| dashboards.copy | v1alpha | | | +| dashboards.create | v1alpha | | | +| dashboards.delete | v1alpha | | | +| dashboards.get | v1alpha | | | +| dashboards.list | v1alpha | | | +| dataAccessLabels.create | v1alpha | | | +| dataAccessLabels.delete | v1alpha | | | +| dataAccessLabels.get | v1alpha | | | +| dataAccessLabels.list | v1alpha | | | +| dataAccessLabels.patch | v1alpha | | | +| dataAccessScopes.create | v1alpha | | | +| dataAccessScopes.delete | v1alpha | | | +| dataAccessScopes.get | v1alpha | | | +| dataAccessScopes.list | v1alpha | | | +| dataAccessScopes.patch | v1alpha | | | +| dataExports.cancel | v1alpha | chronicle.data_export.cancel_data_export | secops export cancel | +| dataExports.create | v1alpha | chronicle.data_export.create_data_export | secops export create | +| dataExports.fetchavailablelogtypes | v1alpha | chronicle.data_export.fetch_available_log_types | secops export log-types | +| dataExports.get | v1alpha | chronicle.data_export.get_data_export | secops export status | +| dataExports.list | v1alpha | chronicle.data_export.list_data_export | secops export list | +| dataExports.patch | v1alpha | chronicle.data_export.update_data_export | secops export update | +| dataTableOperationErrors.get | v1alpha | | | +| dataTables.create | v1alpha | chronicle.data_table.create_data_table | secops data-table create | +| dataTables.dataTableRows.bulkCreate | v1alpha | chronicle.data_table.create_data_table_rows | secops data-table add-rows | +| dataTables.dataTableRows.bulkCreateAsync | v1alpha | | | +| dataTables.dataTableRows.bulkGet | v1alpha | | | +| dataTables.dataTableRows.bulkReplace | v1alpha | chronicle.data_table.replace_data_table_rows | secops data-table replace-rows | +| dataTables.dataTableRows.bulkReplaceAsync | v1alpha | | | +| dataTables.dataTableRows.bulkUpdate | v1alpha | chronicle.data_table.update_data_table_rows | secops data-table update-rows | +| dataTables.dataTableRows.bulkUpdateAsync | v1alpha | | | +| dataTables.dataTableRows.create | v1alpha | | | +| dataTables.dataTableRows.delete | v1alpha | chronicle.data_table.delete_data_table_rows | secops data-table delete-rows | +| dataTables.dataTableRows.get | v1alpha | | | +| dataTables.dataTableRows.list | v1alpha | chronicle.data_table.list_data_table_rows | secops data-table list-rows | +| dataTables.dataTableRows.patch | v1alpha | | | +| dataTables.delete | v1alpha | chronicle.data_table.delete_data_table | secops data-table delete | +| dataTables.get | v1alpha | chronicle.data_table.get_data_table | secops data-table get | +| dataTables.list | v1alpha | chronicle.data_table.list_data_tables | secops data-table list | +| dataTables.patch | v1alpha | | | +| dataTables.upload | v1alpha | | | +| dataTaps.create | v1alpha | | | +| dataTaps.delete | v1alpha | | | +| dataTaps.get | v1alpha | | | +| dataTaps.list | v1alpha | | | +| dataTaps.patch | v1alpha | | | +| delete | v1alpha | | | +| enrichmentControls.create | v1alpha | | | +| enrichmentControls.delete | v1alpha | | | +| enrichmentControls.get | v1alpha | | | +| enrichmentControls.list | v1alpha | | | +| entities.get | v1alpha | | | +| entities.import | v1alpha | chronicle.log_ingest.import_entities | secops entity import | +| entities.modifyEntityRiskScore | v1alpha | | | +| entities.queryEntityRiskScoreModifications | v1alpha | | | +| entityRiskScores.query | v1alpha | | | +| errorNotificationConfigs.create | v1alpha | | | +| errorNotificationConfigs.delete | v1alpha | | | +| errorNotificationConfigs.get | v1alpha | | | +| errorNotificationConfigs.list | v1alpha | | | +| errorNotificationConfigs.patch | v1alpha | | | +| events.batchGet | v1alpha | | | +| events.get | v1alpha | | | +| events.import | v1alpha | chronicle.log_ingest.ingest_udm | secops log ingest-udm | +| extractSyslog | v1alpha | | | +| federationGroups.create | v1alpha | | | +| federationGroups.delete | v1alpha | | | +| federationGroups.get | v1alpha | | | +| federationGroups.list | v1alpha | | | +| federationGroups.patch | v1alpha | | | +| feedPacks.get | v1alpha | | | +| feedPacks.list | v1alpha | | | +| feedServiceAccounts.fetchServiceAccountForCustomer | v1alpha | | | +| feedSourceTypeSchemas.list | v1alpha | | | +| feedSourceTypeSchemas.logTypeSchemas.list | v1alpha | | | +| feeds.create | v1alpha | chronicle.feeds.create_feed | secops feed create | +| feeds.delete | v1alpha | chronicle.feeds.delete_feed | secops feed delete | +| feeds.disable | v1alpha | chronicle.feeds.disable_feed | secops feed disable | +| feeds.enable | v1alpha | chronicle.feeds.enable_feed | secops feed enable | +| feeds.generateSecret | v1alpha | chronicle.feeds.generate_secret | secops feed secret | +| feeds.get | v1alpha | chronicle.feeds.get_feed | secops feed get | +| feeds.importPushLogs | v1alpha | | | +| feeds.list | v1alpha | chronicle.feeds.list_feeds | secops feed list | +| feeds.patch | v1alpha | chronicle.feeds.update_feed | secops feed update | +| feeds.scheduleTransfer | v1alpha | | | +| fetchFederationAccess | v1alpha | | | +| findEntity | v1alpha | | | +| findEntityAlerts | v1alpha | | | +| findRelatedEntities | v1alpha | | | +| findUdmFieldValues | v1alpha | | | +| findingsGraph.exploreNode | v1alpha | | | +| findingsGraph.initializeGraph | v1alpha | | | +| findingsRefinements.computeFindingsRefinementActivity | v1alpha | chronicle.rule_exclusion.compute_rule_exclusion_activity | secops rule-exclusion compute-activity | +| findingsRefinements.create | v1alpha | chronicle.rule_exclusion.create_rule_exclusion | secops rule-exclusion create | +| findingsRefinements.get | v1alpha | chronicle.rule_exclusion.get_rule_exclusion | secops rule-exclusion get | +| findingsRefinements.getDeployment | v1alpha | chronicle.rule_exclusion.get_rule_exclusion_deployment | secops rule-exclusion get-deployment | +| findingsRefinements.list | v1alpha | chronicle.rule_exclusion.list_rule_exclusions | secops rule-exclusion list | +| findingsRefinements.patch | v1alpha | chronicle.rule_exclusion.patch_rule_exclusion | secops rule-exclusion update | +| findingsRefinements.updateDeployment | v1alpha | chronicle.rule_exclusion.update_rule_exclusion_deployment | secops rule-exclusion update-deployment | +| forwarders.collectors.create | v1alpha | | | +| forwarders.collectors.delete | v1alpha | | | +| forwarders.collectors.get | v1alpha | | | +| forwarders.collectors.list | v1alpha | | | +| forwarders.collectors.patch | v1alpha | | | +| forwarders.create | v1alpha | chronicle.log_ingest.create_forwarder | secops forwarder create | +| forwarders.delete | v1alpha | chronicle.log_ingest.delete_forwarder | secops forwarder delete | +| forwarders.generateForwarderFiles | v1alpha | | | +| forwarders.get | v1alpha | chronicle.log_ingest.get_forwarder | secops forwarder get | +| forwarders.importStatsEvents | v1alpha | | | +| forwarders.list | v1alpha | chronicle.log_ingest.list_forwarder | secops forwarder list | +| forwarders.patch | v1alpha | chronicle.log_ingest.update_forwarder | secops forwarder update | +| generateCollectionAgentAuth | v1alpha | | | +| generateSoarAuthJwt | v1alpha | | | +| generateUdmKeyValueMappings | v1alpha | | | +| generateWorkspaceConnectionToken | v1alpha | | | +| get | v1alpha | | | +| getBigQueryExport | v1alpha | | | +| getMultitenantDirectory | v1alpha | | | +| getRiskConfig | v1alpha | | | +| ingestionLogLabels.get | v1alpha | | | +| ingestionLogLabels.list | v1alpha | | | +| ingestionLogNamespaces.get | v1alpha | | | +| ingestionLogNamespaces.list | v1alpha | | | +| integrations.create | v1alpha | | | +| integrations.delete | v1alpha | chronicle.soar.integration.integrations.delete_integration(api_version=APIVersion.V1ALPHA) | secops integration integrations delete | +| integrations.download | v1alpha | chronicle.soar.integration.integrations.download_integration(api_version=APIVersion.V1ALPHA) | secops integration integrations download | +| integrations.downloadDependency | v1alpha | chronicle.soar.integration.integrations.download_integration_dependency(api_version=APIVersion.V1ALPHA) | secops integration integrations download-dependency | +| integrations.exportIntegrationItems | v1alpha | chronicle.soar.integration.integrations.export_integration_items(api_version=APIVersion.V1ALPHA) | secops integration integrations export-items | +| integrations.fetchAffectedItems | v1alpha | chronicle.soar.integration.integrations.get_integration_affected_items(api_version=APIVersion.V1ALPHA) | secops integration integrations get-affected-items | +| integrations.fetchAgentIntegrations | v1alpha | chronicle.soar.integration.integrations.get_agent_integrations(api_version=APIVersion.V1ALPHA) | secops integration integrations get-agent | +| integrations.fetchCommercialDiff | v1alpha | chronicle.soar.integration.integrations.get_integration_diff(api_version=APIVersion.V1ALPHA) | secops integration integrations get-diff | +| integrations.fetchDependencies | v1alpha | chronicle.soar.integration.integrations.get_integration_dependencies(api_version=APIVersion.V1ALPHA) | secops integration integrations get-dependencies | +| integrations.fetchRestrictedAgents | v1alpha | chronicle.soar.integration.integrations.get_integration_restricted_agents(api_version=APIVersion.V1ALPHA) | secops integration integrations get-restricted-agents | +| integrations.get | v1alpha | chronicle.soar.integration.integrations.get_integration(api_version=APIVersion.V1ALPHA) | secops integration integrations get | +| integrations.getFetchProductionDiff | v1alpha | chronicle.soar.integration.integrations.get_integration_diff(api_version=APIVersion.V1ALPHA, diff_type=DiffType.PRODUCTION) | secops integration integrations get-diff | +| integrations.getFetchStagingDiff | v1alpha | chronicle.soar.integration.integrations.get_integration_diffapi_version=APIVersion.V1ALPHA, (diff_type=DiffType.STAGING) | secops integration integrations get-diff | +| integrations.import | v1alpha | | | +| integrations.importIntegrationDependency | v1alpha | | | +| integrations.importIntegrationItems | v1alpha | | | +| integrations.list | v1alpha | chronicle.soar.integration.integrations.list_integrations(api_version=APIVersion.V1ALPHA) | secops integration integrations list | +| integrations.patch | v1alpha | | | +| integrations.pushToProduction | v1alpha | chronicle.soar.integration.integrations.transition_integration(api_version=APIVersion.V1ALPHA, target_mode=TargetMode.PRODUCTION) | secops integration integrations transition | +| integrations.pushToStaging | v1alpha | chronicle.soar.integration.integrations.transition_integration(api_version=APIVersion.V1ALPHA, target_mode=TargetMode.STAGING) | secops integration integrations transition | +| integrations.updateCustomIntegration | v1alpha | | | +| integrations.upload | v1alpha | | | +| integrations.actions.create | v1alpha | chronicle.soar.integration.actions.create_integration_action(api_version=APIVersion.V1ALPHA) | secops integration actions create | +| integrations.actions.delete | v1alpha | chronicle.soar.integration.actions.delete_integration_action(api_version=APIVersion.V1ALPHA) | secops integration actions delete | +| integrations.actions.executeTest | v1alpha | chronicle.soar.integration.actions.execute_integration_action_test(api_version=APIVersion.V1ALPHA) | secops integration actions test | +| integrations.actions.fetchActionsByEnvironment | v1alpha | chronicle.soar.integration.actions.get_integration_actions_by_environment(api_version=APIVersion.V1ALPHA) | | +| integrations.actions.fetchTemplate | v1alpha | chronicle.soar.integration.actions.get_integration_action_template(api_version=APIVersion.V1ALPHA) | secops integration actions template | +| integrations.actions.get | v1alpha | chronicle.soar.integration.actions.get_integration_action(api_version=APIVersion.V1ALPHA) | secops integration actions get | +| integrations.actions.list | v1alpha | chronicle.soar.integration.actions.list_integration_actions(api_version=APIVersion.V1ALPHA) | secops integration actions list | +| integrations.actions.patch | v1alpha | chronicle.soar.integration.actions.update_integration_action(api_version=APIVersion.V1ALPHA) | secops integration actions update | +| integrations.actions.revisions.create | v1alpha | chronicle.soar.integration.action_revisions.create_integration_action_revision(api_version=APIVersion.V1ALPHA) | secops integration action-revisions create | +| integrations.actions.revisions.delete | v1alpha | chronicle.soar.integration.action_revisions.delete_integration_action_revision(api_version=APIVersion.V1ALPHA) | secops integration action-revisions delete | +| integrations.actions.revisions.list | v1alpha | chronicle.soar.integration.action_revisions.list_integration_action_revisions(api_version=APIVersion.V1ALPHA) | secops integration action-revisions list | +| integrations.actions.revisions.rollback | v1alpha | chronicle.soar.integration.action_revisions.rollback_integration_action_revision(api_version=APIVersion.V1ALPHA) | secops integration action-revisions rollback | +| integrations.connectors.create | v1alpha | chronicle.soar.integration.connectors.create_integration_connector(api_version=APIVersion.V1ALPHA) | secops integration connectors create | +| integrations.connectors.delete | v1alpha | chronicle.soar.integration.connectors.delete_integration_connector(api_version=APIVersion.V1ALPHA) | secops integration connectors delete | +| integrations.connectors.executeTest | v1alpha | chronicle.soar.integration.connectors.execute_integration_connector_test(api_version=APIVersion.V1ALPHA) | secops integration connectors test | +| integrations.connectors.fetchTemplate | v1alpha | chronicle.soar.integration.connectors.get_integration_connector_template(api_version=APIVersion.V1ALPHA) | secops integration connectors template | +| integrations.connectors.get | v1alpha | chronicle.soar.integration.connectors.get_integration_connector(api_version=APIVersion.V1ALPHA) | secops integration connectors get | +| integrations.connectors.list | v1alpha | chronicle.soar.integration.connectors.list_integration_connectors(api_version=APIVersion.V1ALPHA) | secops integration connectors list | +| integrations.connectors.patch | v1alpha | chronicle.soar.integration.connectors.update_integration_connector(api_version=APIVersion.V1ALPHA) | secops integration connectors update | +| integrations.connectors.revisions.create | v1alpha | chronicle.soar.integration.connector_revisions.create_integration_connector_revision(api_version=APIVersion.V1ALPHA) | secops integration connector-revisions create | +| integrations.connectors.revisions.delete | v1alpha | chronicle.soar.integration.connector_revisions.delete_integration_connector_revision(api_version=APIVersion.V1ALPHA) | secops integration connector-revisions delete | +| integrations.connectors.revisions.list | v1alpha | chronicle.soar.integration.connector_revisions.list_integration_connector_revisions(api_version=APIVersion.V1ALPHA) | secops integration connector-revisions list | +| integrations.connectors.revisions.rollback | v1alpha | chronicle.soar.integration.connector_revisions.rollback_integration_connector_revision(api_version=APIVersion.V1ALPHA) | secops integration connector-revisions rollback | +| integrations.connectors.contextProperties.clearAll | v1alpha | chronicle.soar.integration.connector_context_properties.delete_all_connector_context_properties(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties delete-all | +| integrations.connectors.contextProperties.create | v1alpha | chronicle.soar.integration.connector_context_properties.create_connector_context_property(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties create | +| integrations.connectors.contextProperties.delete | v1alpha | chronicle.soar.integration.connector_context_properties.delete_connector_context_property(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties delete | +| integrations.connectors.contextProperties.get | v1alpha | chronicle.soar.integration.connector_context_properties.get_connector_context_property(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties get | +| integrations.connectors.contextProperties.list | v1alpha | chronicle.soar.integration.connector_context_properties.list_connector_context_properties(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties list | +| integrations.connectors.contextProperties.patch | v1alpha | chronicle.soar.integration.connector_context_properties.update_connector_context_property(api_version=APIVersion.V1ALPHA) | secops integration connector-context-properties update | +| integrations.connectors.connectorInstances.logs.get | v1alpha | chronicle.soar.integration.connector_instance_logs.get_connector_instance_log(api_version=APIVersion.V1ALPHA) | secops integration connector-instance-logs get | +| integrations.connectors.connectorInstances.logs.list | v1alpha | chronicle.soar.integration.connector_instance_logs.list_connector_instance_logs(api_version=APIVersion.V1ALPHA) | secops integration connector-instance-logs list | +| integrations.connectors.connectorInstances.create | v1alpha | chronicle.soar.integration.connector_instances.create_connector_instance(api_version=APIVersion.V1ALPHA) | secops integration connector-instances create | +| integrations.connectors.connectorInstances.delete | v1alpha | chronicle.soar.integration.connector_instances.delete_connector_instance(api_version=APIVersion.V1ALPHA) | secops integration connector-instances delete | +| integrations.connectors.connectorInstances.fetchLatestDefinition | v1alpha | chronicle.soar.integration.connector_instances.get_connector_instance_latest_definition(api_version=APIVersion.V1ALPHA) | secops integration connector-instances get-latest-definition | +| integrations.connectors.connectorInstances.get | v1alpha | chronicle.soar.integration.connector_instances.get_connector_instance(api_version=APIVersion.V1ALPHA) | secops integration connector-instances get | +| integrations.connectors.connectorInstances.list | v1alpha | chronicle.soar.integration.connector_instances.list_connector_instances(api_version=APIVersion.V1ALPHA) | secops integration connector-instances list | +| integrations.connectors.connectorInstances.patch | v1alpha | chronicle.soar.integration.connector_instances.update_connector_instance(api_version=APIVersion.V1ALPHA) | secops integration connector-instances update | +| integrations.connectors.connectorInstances.runOnDemand | v1alpha | chronicle.soar.integration.connector_instances.run_connector_instance_on_demand(api_version=APIVersion.V1ALPHA) | secops integration connector-instances run-on-demand | +| integrations.connectors.connectorInstances.setLogsCollection | v1alpha | chronicle.soar.integration.connector_instances.set_connector_instance_logs_collection(api_version=APIVersion.V1ALPHA) | secops integration connector-instances set-logs-collection | +| integrations.integrationInstances.create | v1alpha | chronicle.soar.integration.integration_instances.create_integration_instance(api_version=APIVersion.V1ALPHA) | secops integration instances create | +| integrations.integrationInstances.delete | v1alpha | chronicle.soar.integration.integration_instances.delete_integration_instance(api_version=APIVersion.V1ALPHA) | secops integration instances delete | +| integrations.integrationInstances.executeTest | v1alpha | chronicle.soar.integration.integration_instances.execute_integration_instance_test(api_version=APIVersion.V1ALPHA) | secops integration instances test | +| integrations.integrationInstances.fetchAffectedItems | v1alpha | chronicle.soar.integration.integration_instances.get_integration_instance_affected_items(api_version=APIVersion.V1ALPHA) | secops integration instances get-affected-items | +| integrations.integrationInstances.fetchDefaultInstance | v1alpha | chronicle.soar.integration.integration_instances.get_default_integration_instance(api_version=APIVersion.V1ALPHA) | secops integration instances get-default | +| integrations.integrationInstances.get | v1alpha | chronicle.soar.integration.integration_instances.get_integration_instance(api_version=APIVersion.V1ALPHA) | secops integration instances get | +| integrations.integrationInstances.list | v1alpha | chronicle.soar.integration.integration_instances.list_integration_instances(api_version=APIVersion.V1ALPHA) | secops integration instances list | +| integrations.integrationInstances.patch | v1alpha | chronicle.soar.integration.integration_instances.update_integration_instance(api_version=APIVersion.V1ALPHA) | secops integration instances update | +| integrations.transformers.create | v1alpha | chronicle.soar.integration.transformers.create_integration_transformer | secops integration transformers create | +| integrations.transformers.delete | v1alpha | chronicle.soar.integration.transformers.delete_integration_transformer | secops integration transformers delete | +| integrations.transformers.executeTest | v1alpha | chronicle.soar.integration.transformers.execute_integration_transformer_test | secops integration transformers test | +| integrations.transformers.fetchTemplate | v1alpha | chronicle.soar.integration.transformers.get_integration_transformer_template | secops integration transformers template | +| integrations.transformers.get | v1alpha | chronicle.soar.integration.transformers.get_integration_transformer | secops integration transformers get | +| integrations.transformers.list | v1alpha | chronicle.soar.integration.transformers.list_integration_transformers | secops integration transformers list | +| integrations.transformers.patch | v1alpha | chronicle.soar.integration.transformers.update_integration_transformer | secops integration transformers update | +| integrations.transformers.revisions.create | v1alpha | chronicle.soar.integration.transformer_revisions.create_integration_transformer_revision | secops integration transformer-revisions create | +| integrations.transformers.revisions.delete | v1alpha | chronicle.soar.integration.transformer_revisions.delete_integration_transformer_revision | secops integration transformer-revisions delete | +| integrations.transformers.revisions.list | v1alpha | chronicle.soar.integration.transformer_revisions.list_integration_transformer_revisions | secops integration transformer-revisions list | +| integrations.transformers.revisions.rollback | v1alpha | chronicle.soar.integration.transformer_revisions.rollback_integration_transformer_revision | secops integration transformer-revisions rollback | +| integrations.logicalOperators.create | v1alpha | chronicle.soar.integration.logical_operators.create_integration_logical_operator | secops integration logical-operators create | +| integrations.logicalOperators.delete | v1alpha | chronicle.soar.integration.logical_operators.delete_integration_logical_operator | secops integration logical-operators delete | +| integrations.logicalOperators.executeTest | v1alpha | chronicle.soar.integration.logical_operators.execute_integration_logical_operator_test | secops integration logical-operators test | +| integrations.logicalOperators.fetchTemplate | v1alpha | chronicle.soar.integration.logical_operators.get_integration_logical_operator_template | secops integration logical-operators template | +| integrations.logicalOperators.get | v1alpha | chronicle.soar.integration.logical_operators.get_integration_logical_operator | secops integration logical-operators get | +| integrations.logicalOperators.list | v1alpha | chronicle.soar.integration.logical_operators.list_integration_logical_operators | secops integration logical-operators list | +| integrations.logicalOperators.patch | v1alpha | chronicle.soar.integration.logical_operators.update_integration_logical_operator | secops integration logical-operators update | +| integrations.logicalOperators.revisions.create | v1alpha | chronicle.soar.integration.logical_operator_revisions.create_integration_logical_operator_revision | secops integration logical-operator-revisions create | +| integrations.logicalOperators.revisions.delete | v1alpha | chronicle.soar.integration.logical_operator_revisions.delete_integration_logical_operator_revision | secops integration logical-operator-revisions delete | +| integrations.logicalOperators.revisions.list | v1alpha | chronicle.soar.integration.logical_operator_revisions.list_integration_logical_operator_revisions | secops integration logical-operator-revisions list | +| integrations.logicalOperators.revisions.rollback | v1alpha | chronicle.soar.integration.logical_operator_revisions.rollback_integration_logical_operator_revision | secops integration logical-operator-revisions rollback | +| integrations.jobs.create | v1alpha | chronicle.soar.integration.jobs.create_integration_job(api_version=APIVersion.V1ALPHA) | secops integration jobs create | +| integrations.jobs.delete | v1alpha | chronicle.soar.integration.jobs.delete_integration_job(api_version=APIVersion.V1ALPHA) | secops integration jobs delete | +| integrations.jobs.executeTest | v1alpha | chronicle.soar.integration.jobs.execute_integration_job_test(api_version=APIVersion.V1ALPHA) | secops integration jobs test | +| integrations.jobs.fetchTemplate | v1alpha | chronicle.soar.integration.jobs.get_integration_job_template(api_version=APIVersion.V1ALPHA) | secops integration jobs template | +| integrations.jobs.get | v1alpha | chronicle.soar.integration.jobs.get_integration_job(api_version=APIVersion.V1ALPHA) | secops integration jobs get | +| integrations.jobs.list | v1alpha | chronicle.soar.integration.jobs.list_integration_jobs(api_version=APIVersion.V1ALPHA) | secops integration jobs list | +| integrations.jobs.patch | v1alpha | chronicle.soar.integration.jobs.update_integration_job(api_version=APIVersion.V1ALPHA) | secops integration jobs update | +| integrations.managers.create | v1alpha | chronicle.soar.integration.managers.create_integration_manager(api_version=APIVersion.V1ALPHA) | secops integration managers create | +| integrations.managers.delete | v1alpha | chronicle.soar.integration.managers.delete_integration_manager(api_version=APIVersion.V1ALPHA) | secops integration managers delete | +| integrations.managers.fetchTemplate | v1alpha | chronicle.soar.integration.managers.get_integration_manager_template(api_version=APIVersion.V1ALPHA) | secops integration managers template | +| integrations.managers.get | v1alpha | chronicle.soar.integration.managers.get_integration_manager(api_version=APIVersion.V1ALPHA) | secops integration managers get | +| integrations.managers.list | v1alpha | chronicle.soar.integration.managers.list_integration_managers(api_version=APIVersion.V1ALPHA) | secops integration managers list | +| integrations.managers.patch | v1alpha | chronicle.soar.integration.managers.update_integration_manager(api_version=APIVersion.V1ALPHA) | secops integration managers update | +| integrations.managers.revisions.create | v1alpha | chronicle.soar.integration.manager_revisions.create_integration_manager_revision(api_version=APIVersion.V1ALPHA) | secops integration manager-revisions create | +| integrations.managers.revisions.delete | v1alpha | chronicle.soar.integration.manager_revisions.delete_integration_manager_revision(api_version=APIVersion.V1ALPHA) | secops integration manager-revisions delete | +| integrations.managers.revisions.get | v1alpha | chronicle.soar.integration.manager_revisions.get_integration_manager_revision(api_version=APIVersion.V1ALPHA) | secops integration manager-revisions get | +| integrations.managers.revisions.list | v1alpha | chronicle.soar.integration.manager_revisions.list_integration_manager_revisions(api_version=APIVersion.V1ALPHA) | secops integration manager-revisions list | +| integrations.managers.revisions.rollback | v1alpha | chronicle.soar.integration.manager_revisions.rollback_integration_manager_revision(api_version=APIVersion.V1ALPHA) | secops integration manager-revisions rollback | +| integrations.jobs.revisions.create | v1alpha | chronicle.soar.integration.job_revisions.create_integration_job_revision(api_version=APIVersion.V1ALPHA) | secops integration job-revisions create | +| integrations.jobs.revisions.delete | v1alpha | chronicle.soar.integration.job_revisions.delete_integration_job_revision(api_version=APIVersion.V1ALPHA) | secops integration job-revisions delete | +| integrations.jobs.revisions.list | v1alpha | chronicle.soar.integration.job_revisions.list_integration_job_revisions(api_version=APIVersion.V1ALPHA) | secops integration job-revisions list | +| integrations.jobs.revisions.rollback | v1alpha | chronicle.soar.integration.job_revisions.rollback_integration_job_revision(api_version=APIVersion.V1ALPHA) | secops integration job-revisions rollback | +| integrations.jobs.jobInstances.create | v1alpha | chronicle.soar.integration.job_instances.create_integration_job_instance(api_version=APIVersion.V1ALPHA) | secops integration job-instances create | +| integrations.jobs.jobInstances.delete | v1alpha | chronicle.soar.integration.job_instances.delete_integration_job_instance(api_version=APIVersion.V1ALPHA) | secops integration job-instances delete | +| integrations.jobs.jobInstances.get | v1alpha | chronicle.soar.integration.job_instances.get_integration_job_instance(api_version=APIVersion.V1ALPHA) | secops integration job-instances get | +| integrations.jobs.jobInstances.list | v1alpha | chronicle.soar.integration.job_instances.list_integration_job_instances(api_version=APIVersion.V1ALPHA) | secops integration job-instances list | +| integrations.jobs.jobInstances.patch | v1alpha | chronicle.soar.integration.job_instances.update_integration_job_instance(api_version=APIVersion.V1ALPHA) | secops integration job-instances update | +| integrations.jobs.jobInstances.runOnDemand | v1alpha | chronicle.soar.integration.job_instances.run_integration_job_instance_on_demand(api_version=APIVersion.V1ALPHA) | secops integration job-instances run-on-demand | +| integrations.jobs.contextProperties.clearAll | v1alpha | chronicle.soar.integration.job_context_properties.delete_all_job_context_properties(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties delete-all | +| integrations.jobs.contextProperties.create | v1alpha | chronicle.soar.integration.job_context_properties.create_job_context_property(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties create | +| integrations.jobs.contextProperties.delete | v1alpha | chronicle.soar.integration.job_context_properties.delete_job_context_property(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties delete | +| integrations.jobs.contextProperties.get | v1alpha | chronicle.soar.integration.job_context_properties.get_job_context_property(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties get | +| integrations.jobs.contextProperties.list | v1alpha | chronicle.soar.integration.job_context_properties.list_job_context_properties(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties list | +| integrations.jobs.contextProperties.patch | v1alpha | chronicle.soar.integration.job_context_properties.update_job_context_property(api_version=APIVersion.V1ALPHA) | secops integration job-context-properties update | +| integrations.jobs.jobInstances.logs.get | v1alpha | chronicle.soar.integration.job_instance_logs.get_job_instance_log(api_version=APIVersion.V1ALPHA) | secops integration job-instance-logs get | +| integrations.jobs.jobInstances.logs.list | v1alpha | chronicle.soar.integration.job_instance_logs.list_job_instance_logs(api_version=APIVersion.V1ALPHA) | secops integration job-instance-logs list | +| investigations.fetchAssociated | v1alpha | chronicle.investigations.fetch_associated_investigations | secops investigation fetch-associated | +| investigations.get | v1alpha | chronicle.investigations.get_investigation | secops investigation get | +| investigations.list | v1alpha | chronicle.investigations.list_investigations | secops investigation list | +| investigations.trigger | v1alpha | chronicle.investigations.trigger_investigation | secops investigation trigger | +| iocs.batchGet | v1alpha | | | +| iocs.findFirstAndLastSeen | v1alpha | | | +| iocs.get | v1alpha | | | +| iocs.getIocState | v1alpha | | | +| iocs.searchCuratedDetectionsForIoc | v1alpha | | | +| iocs.updateIocState | v1alpha | | | +| legacy.legacyBatchGetCases | v1alpha | chronicle.case.get_cases_from_list | secops case | +| legacy.legacyBatchGetCollections | v1alpha | | | +| legacy.legacyCreateOrUpdateCase | v1alpha | | | +| legacy.legacyCreateSoarAlert | v1alpha | | | +| legacy.legacyFetchAlertsView | v1alpha | chronicle.alert.get_alerts | secops alert | +| legacy.legacyFetchUdmSearchCsv | v1alpha | chronicle.udm_search.fetch_udm_search_csv | secops search --csv | +| legacy.legacyFetchUdmSearchView | v1alpha | chronicle.udm_search.fetch_udm_search_view | secops udm-search-view | +| legacy.legacyFindAssetEvents | v1alpha | | | +| legacy.legacyFindRawLogs | v1alpha | | | +| legacy.legacyFindUdmEvents | v1alpha | | | +| legacy.legacyGetAlert | v1alpha | chronicle.rule_alert.get_alert | | +| legacy.legacyGetCuratedRulesTrends | v1alpha | | | +| legacy.legacyGetDetection | v1alpha | | | +| legacy.legacyGetEventForDetection | v1alpha | | | +| legacy.legacyGetRuleCounts | v1alpha | | | +| legacy.legacyGetRulesTrends | v1alpha | | | +| legacy.legacyListCases | v1alpha | chronicle.case.get_cases | secops case --ids | +| legacy.legacyRunTestRule | v1alpha | chronicle.rule.run_rule_test | secops rule validate | +| legacy.legacySearchArtifactEvents | v1alpha | | | +| legacy.legacySearchArtifactIoCDetails | v1alpha | | | +| legacy.legacySearchAssetEvents | v1alpha | | | +| legacy.legacySearchCuratedDetections | v1alpha | | | +| legacy.legacySearchCustomerStats | v1alpha | | | +| legacy.legacySearchDetections | v1alpha | chronicle.rule_detection.list_detections | | +| legacy.legacySearchDomainsRecentlyRegistered | v1alpha | | | +| legacy.legacySearchDomainsTimingStats | v1alpha | | | +| legacy.legacySearchEnterpriseWideAlerts | v1alpha | | | +| legacy.legacySearchEnterpriseWideIoCs | v1alpha | chronicle.ioc.list_iocs | secops iocs | +| legacy.legacySearchFindings | v1alpha | | | +| legacy.legacySearchIngestionStats | v1alpha | | | +| legacy.legacySearchIoCInsights | v1alpha | | | +| legacy.legacySearchRawLogs | v1alpha | | | +| legacy.legacySearchRuleDetectionCountBuckets | v1alpha | | | +| legacy.legacySearchRuleDetectionEvents | v1alpha | | | +| legacy.legacySearchRuleResults | v1alpha | | | +| legacy.legacySearchRulesAlerts | v1alpha | chronicle.rule_alert.search_rule_alerts | | +| legacy.legacySearchUserEvents | v1alpha | | | +| legacy.legacyStreamDetectionAlerts | v1alpha | | | +| legacy.legacyTestRuleStreaming | v1alpha | | | +| legacy.legacyUpdateAlert | v1alpha | chronicle.rule_alert.update_alert | | +| listAllFindingsRefinementDeployments | v1alpha | | | +| logProcessingPipelines.associateStreams | v1alpha | chronicle.log_processing_pipelines.associate_streams | secops log-processing associate-streams | +| logProcessingPipelines.create | v1alpha | chronicle.log_processing_pipelines.create_log_processing_pipeline | secops log-processing create | +| logProcessingPipelines.delete | v1alpha | chronicle.log_processing_pipelines.delete_log_processing_pipeline | secops log-processing delete | +| logProcessingPipelines.dissociateStreams | v1alpha | chronicle.log_processing_pipelines.dissociate_streams | secops log-processing dissociate-streams | +| logProcessingPipelines.fetchAssociatedPipeline | v1alpha | chronicle.log_processing_pipelines.fetch_associated_pipeline | secops log-processing fetch-associated | +| logProcessingPipelines.fetchSampleLogsByStreams | v1alpha | chronicle.log_processing_pipelines.fetch_sample_logs_by_streams | secops log-processing fetch-sample-logs | +| logProcessingPipelines.get | v1alpha | chronicle.log_processing_pipelines.get_log_processing_pipeline | secops log-processing get | +| logProcessingPipelines.list | v1alpha | chronicle.log_processing_pipelines.list_log_processing_pipelines | secops log-processing list | +| logProcessingPipelines.patch | v1alpha | chronicle.log_processing_pipelines.update_log_processing_pipeline | secops log-processing update | +| logProcessingPipelines.testPipeline | v1alpha | chronicle.log_processing_pipelines.test_pipeline | secops log-processing test | +| logTypes.create | v1alpha | | | +| logTypes.generateEventTypesSuggestions | v1alpha | | | +| logTypes.get | v1alpha | | | +| logTypes.getLogTypeSetting | v1alpha | | | +| logTypes.legacySubmitParserExtension | v1alpha | | | +| logTypes.list | v1alpha | | | +| logTypes.logs.export | v1alpha | | | +| logTypes.logs.get | v1alpha | | | +| logTypes.logs.import | v1alpha | chronicle.log_ingest.ingest_log | secops log ingest | +| logTypes.logs.list | v1alpha | | | +| logTypes.parserExtensions.activate | v1alpha | chronicle.parser_extension.activate_parser_extension | secops parser-extension activate | +| logTypes.parserExtensions.create | v1alpha | chronicle.parser_extension.create_parser_extension | secops parser-extension create | +| logTypes.parserExtensions.delete | v1alpha | chronicle.parser_extension.delete_parser_extension | secops parser-extension delete | +| logTypes.parserExtensions.extensionValidationReports.get | v1alpha | | | +| logTypes.parserExtensions.extensionValidationReports.list | v1alpha | | | +| logTypes.parserExtensions.extensionValidationReports.validationErrors.list | v1alpha | | | +| logTypes.parserExtensions.get | v1alpha | chronicle.parser_extension.get_parser_extension | secops parser-extension get | +| logTypes.parserExtensions.list | v1alpha | chronicle.parser_extension.list_parser_extensions | secops parser-extension list | +| logTypes.parserExtensions.validationReports.get | v1alpha | | | +| logTypes.parserExtensions.validationReports.parsingErrors.list | v1alpha | | | +| logTypes.parsers.activate | v1alpha | chronicle.parser.activate_parser | secops parser activate | +| logTypes.parsers.activateReleaseCandidateParser | v1alpha | chronicle.parser.activate_release_candidate | secops parser activate-rc | +| logTypes.parsers.copy | v1alpha | chronicle.parser.copy_parser | secops parser copy | +| logTypes.parsers.create | v1alpha | chronicle.parser.create_parser | secops parser create | +| logTypes.parsers.deactivate | v1alpha | chronicle.parser.deactivate_parser | secops parser deactivate | +| logTypes.parsers.delete | v1alpha | chronicle.parser.delete_parser | secops parser delete | +| logTypes.parsers.get | v1alpha | chronicle.parser.get_parser | secops parser get | +| logTypes.parsers.list | v1alpha | chronicle.parser.list_parsers | secops parser list | +| logTypes.parsers.validationReports.get | v1alpha | | | +| logTypes.parsers.validationReports.parsingErrors.list | v1alpha | | | +| logTypes.patch | v1alpha | | | +| logTypes.runParser | v1alpha | chronicle.parser.run_parser | secops parser run | +| logTypes.updateLogTypeSetting | v1alpha | | | +| logs.classify | v1alpha | chronicle.log_types.classify_logs | secops log classify | +| marketplaceIntegrations.get | v1alpha | chronicle.marketplace_integrations.get_marketplace_integration(api_version=APIVersion.V1ALPHA) | secops integration marketplace get | +| marketplaceIntegrations.getDiff | v1alpha | chronicle.marketplace_integrations.get_marketplace_integration_diff(api_version=APIVersion.V1ALPHA) | secops integration marketplace diff | +| marketplaceIntegrations.install | v1alpha | chronicle.marketplace_integrations.install_marketplace_integration(api_version=APIVersion.V1ALPHA) | secops integration marketplace install | +| marketplaceIntegrations.list | v1alpha | chronicle.marketplace_integrations.list_marketplace_integrations(api_version=APIVersion.V1ALPHA) | secops integration marketplace list | +| marketplaceIntegrations.uninstall | v1alpha | chronicle.marketplace_integrations.uninstall_marketplace_integration(api_version=APIVersion.V1ALPHA) | secops integration marketplace uninstall | +| nativeDashboards.addChart | v1alpha | chronicle.dashboard.add_chart | secops dashboard add-chart | +| nativeDashboards.create | v1alpha | chronicle.dashboard.create_dashboard | secops dashboard create | +| nativeDashboards.delete | v1alpha | chronicle.dashboard.delete_dashboard | secops dashboard delete | +| nativeDashboards.duplicate | v1alpha | chronicle.dashboard.duplicate_dashboard | secops dashboard duplicate | +| nativeDashboards.duplicateChart | v1alpha | | | +| nativeDashboards.editChart | v1alpha | chronicle.dashboard.edit_chart | secops dashboard edit-chart | +| nativeDashboards.export | v1alpha | chronicle.dashboard.export_dashboard | secops dashboard export | +| nativeDashboards.get | v1alpha | chronicle.dashboard.get_dashboard | secops dashboard get | +| nativeDashboards.import | v1alpha | chronicle.dashboard.import_dashboard | secops dashboard import | +| nativeDashboards.list | v1alpha | chronicle.dashboard.list_dashboards | secops dashboard list | +| nativeDashboards.patch | v1alpha | chronicle.dashboard.update_dashboard | secops dashboard update | +| nativeDashboards.removeChart | v1alpha | chronicle.dashboard.remove_chart | secops dashboard remove-chart | +| operations.cancel | v1alpha | | | +| operations.delete | v1alpha | | | +| operations.get | v1alpha | | | +| operations.list | v1alpha | | | +| operations.streamSearch | v1alpha | | | +| queryProductSourceStats | v1alpha | | | +| referenceLists.create | v1alpha | | | +| referenceLists.get | v1alpha | | | +| referenceLists.list | v1alpha | | | +| referenceLists.patch | v1alpha | | | +| report | v1alpha | | | +| ruleExecutionErrors.list | v1alpha | chronicle.rule_detection.list_errors | | +| rules.create | v1alpha | | | +| rules.delete | v1alpha | | | +| rules.deployments.list | v1alpha | | | +| rules.get | v1alpha | | | +| rules.getDeployment | v1alpha | | | +| rules.list | v1alpha | | | +| rules.listRevisions | v1alpha | | | +| rules.patch | v1alpha | | | +| rules.retrohunts.create | v1alpha | | | +| rules.retrohunts.get | v1alpha | | | +| rules.retrohunts.list | v1alpha | | | +| rules.updateDeployment | v1alpha | | | +| searchEntities | v1alpha | | | +| searchRawLogs | v1alpha | | | +| summarizeEntitiesFromQuery | v1alpha | chronicle.entity.summarize_entity | secops entity | +| summarizeEntity | v1alpha | chronicle.entity.summarize_entity | | +| testFindingsRefinement | v1alpha | | | +| translateUdmQuery | v1alpha | chronicle.nl_search.translate_nl_to_udm | | +| translateYlRule | v1alpha | | | +| udmSearch | v1alpha | chronicle.search.search_udm | secops search | +| undelete | v1alpha | | | +| updateBigQueryExport | v1alpha | | | +| updateRiskConfig | v1alpha | | | +| users.clearConversationHistory | v1alpha | | | +| users.conversations.create | v1alpha | chronicle.gemini.create_conversation | | +| users.conversations.delete | v1alpha | | | +| users.conversations.get | v1alpha | | | +| users.conversations.list | v1alpha | | | +| users.conversations.messages.create | v1alpha | chronicle.gemini.query_gemini | secops gemini | +| users.conversations.messages.delete | v1alpha | | | +| users.conversations.messages.get | v1alpha | | | +| users.conversations.messages.list | v1alpha | | | +| users.conversations.messages.patch | v1alpha | | | +| users.conversations.patch | v1alpha | | | +| users.getPreferenceSet | v1alpha | chronicle.gemini.opt_in_to_gemini | secops gemini --opt-in | +| users.searchQueries.create | v1alpha | | | +| users.searchQueries.delete | v1alpha | | | +| users.searchQueries.get | v1alpha | | | +| users.searchQueries.list | v1alpha | | | +| users.searchQueries.patch | v1alpha | | | +| users.updatePreferenceSet | v1alpha | | | +| validateQuery | v1alpha | chronicle.validate.validate_query | | +| verifyReferenceList | v1alpha | | | +| verifyRuleText | v1alpha | chronicle.rule_validation.validate_rule | secops rule validate | +| watchlists.create | v1alpha | | | +| watchlists.delete | v1alpha | | | +| watchlists.entities.add | v1alpha | | | +| watchlists.entities.batchAdd | v1alpha | | | +| watchlists.entities.batchRemove | v1alpha | | | +| watchlists.entities.remove | v1alpha | | | +| watchlists.get | v1alpha | | | +| watchlists.list | v1alpha | | | +| watchlists.listEntities | v1alpha | | | +| watchlists.patch | v1alpha | | | +| REST Resource | Version | secops-wrapper module | CLI Command | +| -------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | +| dataAccessLabels.create | v1 | | | +| dataAccessLabels.delete | v1 | | | +| dataAccessLabels.get | v1 | | | +| dataAccessLabels.list | v1 | | | +| dataAccessLabels.patch | v1 | | | +| dataAccessScopes.create | v1 | | | +| dataAccessScopes.delete | v1 | | | +| dataAccessScopes.get | v1 | | | +| dataAccessScopes.list | v1 | | | +| dataAccessScopes.patch | v1 | | | +| get | v1 | | | +| operations.cancel | v1 | | | +| operations.delete | v1 | | | +| operations.get | v1 | | | +| operations.list | v1 | | | +| referenceLists.create | v1 | chronicle.reference_list.create_reference_list | secops reference-list create | +| referenceLists.get | v1 | chronicle.reference_list.get_reference_list | secops reference-list get | +| referenceLists.list | v1 | chronicle.reference_list.list_reference_lists | secops reference-list list | +| referenceLists.patch | v1 | chronicle.reference_list.update_reference_list | secops reference-list update | +| rules.create | v1 | chronicle.rule.create_rule | secops rule create | +| rules.delete | v1 | chronicle.rule.delete_rule | secops rule delete | +| rules.deployments.list | v1 | | | +| rules.get | v1 | chronicle.rule.get_rule | secops rule get | +| rules.getDeployment | v1 | | | +| rules.list | v1 | chronicle.rule.list_rules | secops rule list | +| rules.listRevisions | v1 | | | +| rules.patch | v1 | chronicle.rule.update_rule | secops rule update | +| rules.retrohunts.create | v1 | chronicle.rule_retrohunt.create_retrohunt | secops rule-retrohunt create | +| rules.retrohunts.get | v1 | chronicle.rule_retrohunt.get_retrohunt | secops rule-retrohunt get | +| rules.retrohunts.list | v1 | chronicle.rule_retrohunt.list_retrohunts | secops rule-retrohunt list | +| rules.updateDeployment | v1 | chronicle.rule.enable_rule | secops rule enable | +| watchlists.create | v1 | chronicle.watchlist.create_watchlist | secops watchlist create | +| watchlists.delete | v1 | chronicle.watchlist.delete_watchlist | secops watchlist delete | +| watchlists.get | v1 | chronicle.watchlist.get_watchlist | secops watchlist get | +| watchlists.list | v1 | chronicle.watchlist.list_watchlists | secops watchlist list | +| watchlists.patch | v1 | chronicle.watchlist.update_watchlist | secops watchlist update | +| dataAccessLabels.create | v1beta | | | +| dataAccessLabels.delete | v1beta | | | +| dataAccessLabels.get | v1beta | | | +| dataAccessLabels.list | v1beta | | | +| dataAccessLabels.patch | v1beta | | | +| dataAccessScopes.create | v1beta | | | +| dataAccessScopes.delete | v1beta | | | +| dataAccessScopes.get | v1beta | | | +| dataAccessScopes.list | v1beta | | | +| dataAccessScopes.patch | v1beta | | | +| get | v1beta | | | +| operations.cancel | v1beta | | | +| operations.delete | v1beta | | | +| operations.get | v1beta | | | +| operations.list | v1beta | | | +| referenceLists.create | v1beta | | | +| referenceLists.get | v1beta | | | +| referenceLists.list | v1beta | | | +| referenceLists.patch | v1beta | | | +| rules.create | v1beta | | | +| rules.delete | v1beta | | | +| rules.deployments.list | v1beta | | | +| rules.get | v1beta | | | +| rules.getDeployment | v1beta | | | +| rules.list | v1beta | | | +| rules.listRevisions | v1beta | | | +| rules.patch | v1beta | | | +| rules.retrohunts.create | v1beta | | | +| rules.retrohunts.get | v1beta | | | +| rules.retrohunts.list | v1beta | | | +| rules.updateDeployment | v1beta | | | +| watchlists.create | v1beta | | | +| watchlists.delete | v1beta | | | +| watchlists.get | v1beta | | | +| watchlists.list | v1beta | | | +| watchlists.patch | v1beta | | | +| cases.executeBulkAddTag | v1beta | chronicle.case.execute_bulk_add_tag | secops case bulk-add-tag | +| cases.executeBulkAssign | v1beta | chronicle.case.execute_bulk_assign | secops case bulk-assign | +| cases.executeBulkChangePriority | v1beta | chronicle.case.execute_bulk_change_priority | secops case bulk-change-priority | +| cases.executeBulkChangeStage | v1beta | chronicle.case.execute_bulk_change_stage | secops case bulk-change-stage | +| cases.executeBulkClose | v1beta | chronicle.case.execute_bulk_close | secops case bulk-close | +| cases.executeBulkReopen | v1beta | chronicle.case.execute_bulk_reopen | secops case bulk-reopen | +| cases.get | v1beta | chronicle.case.get_case | secops case get | +| cases.list | v1beta | chronicle.case.list_cases | secops case list | +| cases.merge | v1beta | chronicle.case.merge_cases | secops case merge | +| cases.patch | v1beta | chronicle.case.patch_case | secops case update | +| analytics.entities.analyticValues.list | v1alpha | | | +| analytics.list | v1alpha | | | +| batchValidateWatchlistEntities | v1alpha | | | +| bigQueryAccess.provide | v1alpha | | | +| bigQueryExport.provision | v1alpha | | | +| cases.countPriorities | v1alpha | | | +| contentHub.featuredContentRules.list | v1alpha | chronicle.featured_content_rules.list_featured_content_rules | secops featured-content-rules list | +| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.batchUpdate | v1alpha | chronicle.rule_set.batch_update_curated_rule_set_deployments | | +| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.patch | v1alpha | chronicle.rule_set.update_curated_rule_set_deployment | secops curated-rule rule-set-deployment update | +| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.list | v1alpha | chronicle.rule_set.list_curated_rule_set_deployments | secops curated-rule rule-set-deployment list | +| curatedRuleSetCategories.curatedRuleSets.curatedRuleSetDeployments.get | v1alpha | chronicle.rule_set.get_curated_rule_set_deployment
chronicle.rule_set.get_curated_rule_set_deployment_by_name | secops curated-rule rule-set-deployment get | +| curatedRuleSetCategories.curatedRuleSets.get | v1alpha | chronicle.rule_set.get_curated_rule_set | secops curated-rule rule-set get | +| curatedRuleSetCategories.curatedRuleSets.list | v1alpha | chronicle.rule_set.list_curated_rule_sets | secops curated-rule rule-set list | +| curatedRuleSetCategories.get | v1alpha | chronicle.rule_set.get_curated_rule_set_category | secops curated-rule rule-set-category get | +| curatedRuleSetCategories.list | v1alpha | chronicle.rule_set.list_curated_rule_set_categories | secops curated-rule rule-set-category list | +| curatedRules.get | v1alpha | chronicle.rule_set.get_curated_rule
chronicle.rule_set.get_curated_rule_by_name | secops curated-rule rule get | +| curatedRules.list | v1alpha | chronicle.rule_set.list_curated_rules | secops curated-rule rule list | +| dashboardCharts.batchGet | v1alpha | | | +| dashboardCharts.get | v1alpha | chronicle.dashboard.get_chart | secops dashboard get-chart | +| dashboardQueries.execute | v1alpha | chronicle.dashboard_query.execute_query | secops dashboard-query execute | +| dashboardQueries.get | v1alpha | chronicle.dashboard_query.get_execute_query | secops dashboard-query get | +| dashboards.copy | v1alpha | | | +| dashboards.create | v1alpha | | | +| dashboards.delete | v1alpha | | | +| dashboards.get | v1alpha | | | +| dashboards.list | v1alpha | | | +| dataAccessLabels.create | v1alpha | | | +| dataAccessLabels.delete | v1alpha | | | +| dataAccessLabels.get | v1alpha | | | +| dataAccessLabels.list | v1alpha | | | +| dataAccessLabels.patch | v1alpha | | | +| dataAccessScopes.create | v1alpha | | | +| dataAccessScopes.delete | v1alpha | | | +| dataAccessScopes.get | v1alpha | | | +| dataAccessScopes.list | v1alpha | | | +| dataAccessScopes.patch | v1alpha | | | +| dataExports.cancel | v1alpha | chronicle.data_export.cancel_data_export | secops export cancel | +| dataExports.create | v1alpha | chronicle.data_export.create_data_export | secops export create | +| dataExports.fetchavailablelogtypes | v1alpha | chronicle.data_export.fetch_available_log_types | secops export log-types | +| dataExports.get | v1alpha | chronicle.data_export.get_data_export | secops export status | +| dataExports.list | v1alpha | chronicle.data_export.list_data_export | secops export list | +| dataExports.patch | v1alpha | chronicle.data_export.update_data_export | secops export update | +| dataTableOperationErrors.get | v1alpha | | | +| dataTables.create | v1alpha | chronicle.data_table.create_data_table | secops data-table create | +| dataTables.dataTableRows.bulkCreate | v1alpha | chronicle.data_table.create_data_table_rows | secops data-table add-rows | +| dataTables.dataTableRows.bulkCreateAsync | v1alpha | | | +| dataTables.dataTableRows.bulkGet | v1alpha | | | +| dataTables.dataTableRows.bulkReplace | v1alpha | chronicle.data_table.replace_data_table_rows | secops data-table replace-rows | +| dataTables.dataTableRows.bulkReplaceAsync | v1alpha | | | +| dataTables.dataTableRows.bulkUpdate | v1alpha | chronicle.data_table.update_data_table_rows | secops data-table update-rows | +| dataTables.dataTableRows.bulkUpdateAsync | v1alpha | | | +| dataTables.dataTableRows.create | v1alpha | | | +| dataTables.dataTableRows.delete | v1alpha | chronicle.data_table.delete_data_table_rows | secops data-table delete-rows | +| dataTables.dataTableRows.get | v1alpha | | | +| dataTables.dataTableRows.list | v1alpha | chronicle.data_table.list_data_table_rows | secops data-table list-rows | +| dataTables.dataTableRows.patch | v1alpha | | | +| dataTables.delete | v1alpha | chronicle.data_table.delete_data_table | secops data-table delete | +| dataTables.get | v1alpha | chronicle.data_table.get_data_table | secops data-table get | +| dataTables.list | v1alpha | chronicle.data_table.list_data_tables | secops data-table list | +| dataTables.patch | v1alpha | | | +| dataTables.upload | v1alpha | | | +| dataTaps.create | v1alpha | | | +| dataTaps.delete | v1alpha | | | +| dataTaps.get | v1alpha | | | +| dataTaps.list | v1alpha | | | +| dataTaps.patch | v1alpha | | | +| delete | v1alpha | | | +| enrichmentControls.create | v1alpha | | | +| enrichmentControls.delete | v1alpha | | | +| enrichmentControls.get | v1alpha | | | +| enrichmentControls.list | v1alpha | | | +| entities.get | v1alpha | | | +| entities.import | v1alpha | chronicle.log_ingest.import_entities | secops entity import | +| entities.modifyEntityRiskScore | v1alpha | | | +| entities.queryEntityRiskScoreModifications | v1alpha | | | +| entityRiskScores.query | v1alpha | | | +| errorNotificationConfigs.create | v1alpha | | | +| errorNotificationConfigs.delete | v1alpha | | | +| errorNotificationConfigs.get | v1alpha | | | +| errorNotificationConfigs.list | v1alpha | | | +| errorNotificationConfigs.patch | v1alpha | | | +| events.batchGet | v1alpha | | | +| events.get | v1alpha | | | +| events.import | v1alpha | chronicle.log_ingest.ingest_udm | secops log ingest-udm | +| extractSyslog | v1alpha | | | +| federationGroups.create | v1alpha | | | +| federationGroups.delete | v1alpha | | | +| federationGroups.get | v1alpha | | | +| federationGroups.list | v1alpha | | | +| federationGroups.patch | v1alpha | | | +| feedPacks.get | v1alpha | | | +| feedPacks.list | v1alpha | | | +| feedServiceAccounts.fetchServiceAccountForCustomer | v1alpha | | | +| feedSourceTypeSchemas.list | v1alpha | | | +| feedSourceTypeSchemas.logTypeSchemas.list | v1alpha | | | +| feeds.create | v1alpha | chronicle.feeds.create_feed | secops feed create | +| feeds.delete | v1alpha | chronicle.feeds.delete_feed | secops feed delete | +| feeds.disable | v1alpha | chronicle.feeds.disable_feed | secops feed disable | +| feeds.enable | v1alpha | chronicle.feeds.enable_feed | secops feed enable | +| feeds.generateSecret | v1alpha | chronicle.feeds.generate_secret | secops feed secret | +| feeds.get | v1alpha | chronicle.feeds.get_feed | secops feed get | +| feeds.importPushLogs | v1alpha | | | +| feeds.list | v1alpha | chronicle.feeds.list_feeds | secops feed list | +| feeds.patch | v1alpha | chronicle.feeds.update_feed | secops feed update | +| feeds.scheduleTransfer | v1alpha | | | +| fetchFederationAccess | v1alpha | | | +| findEntity | v1alpha | | | +| findEntityAlerts | v1alpha | | | +| findRelatedEntities | v1alpha | | | +| findUdmFieldValues | v1alpha | | | +| findingsGraph.exploreNode | v1alpha | | | +| findingsGraph.initializeGraph | v1alpha | | | +| findingsRefinements.computeFindingsRefinementActivity | v1alpha | chronicle.rule_exclusion.compute_rule_exclusion_activity | secops rule-exclusion compute-activity | +| findingsRefinements.create | v1alpha | chronicle.rule_exclusion.create_rule_exclusion | secops rule-exclusion create | +| findingsRefinements.get | v1alpha | chronicle.rule_exclusion.get_rule_exclusion | secops rule-exclusion get | +| findingsRefinements.getDeployment | v1alpha | chronicle.rule_exclusion.get_rule_exclusion_deployment | secops rule-exclusion get-deployment | +| findingsRefinements.list | v1alpha | chronicle.rule_exclusion.list_rule_exclusions | secops rule-exclusion list | +| findingsRefinements.patch | v1alpha | chronicle.rule_exclusion.patch_rule_exclusion | secops rule-exclusion update | +| findingsRefinements.updateDeployment | v1alpha | chronicle.rule_exclusion.update_rule_exclusion_deployment | secops rule-exclusion update-deployment | +| forwarders.collectors.create | v1alpha | | | +| forwarders.collectors.delete | v1alpha | | | +| forwarders.collectors.get | v1alpha | | | +| forwarders.collectors.list | v1alpha | | | +| forwarders.collectors.patch | v1alpha | | | +| forwarders.create | v1alpha | chronicle.log_ingest.create_forwarder | secops forwarder create | +| forwarders.delete | v1alpha | chronicle.log_ingest.delete_forwarder | secops forwarder delete | +| forwarders.generateForwarderFiles | v1alpha | | | +| forwarders.get | v1alpha | chronicle.log_ingest.get_forwarder | secops forwarder get | +| forwarders.importStatsEvents | v1alpha | | | +| forwarders.list | v1alpha | chronicle.log_ingest.list_forwarder | secops forwarder list | +| forwarders.patch | v1alpha | chronicle.log_ingest.update_forwarder | secops forwarder update | +| generateCollectionAgentAuth | v1alpha | | | +| generateSoarAuthJwt | v1alpha | | | +| generateUdmKeyValueMappings | v1alpha | | | +| generateWorkspaceConnectionToken | v1alpha | | | +| get | v1alpha | | | +| getBigQueryExport | v1alpha | | | +| getMultitenantDirectory | v1alpha | | | +| getRiskConfig | v1alpha | | | +| ingestionLogLabels.get | v1alpha | | | +| ingestionLogLabels.list | v1alpha | | | +| ingestionLogNamespaces.get | v1alpha | | | +| ingestionLogNamespaces.list | v1alpha | | | +| investigations.fetchAssociated | v1alpha | chronicle.investigations.fetch_associated_investigations | secops investigation fetch-associated | +| investigations.get | v1alpha | chronicle.investigations.get_investigation | secops investigation get | +| investigations.list | v1alpha | chronicle.investigations.list_investigations | secops investigation list | +| investigations.trigger | v1alpha | chronicle.investigations.trigger_investigation | secops investigation trigger | +| iocs.batchGet | v1alpha | | | +| iocs.findFirstAndLastSeen | v1alpha | | | +| iocs.get | v1alpha | | | +| iocs.getIocState | v1alpha | | | +| iocs.searchCuratedDetectionsForIoc | v1alpha | | | +| iocs.updateIocState | v1alpha | | | +| legacy.legacyBatchGetCases | v1alpha | chronicle.case.get_cases_from_list | secops case | +| legacy.legacyBatchGetCollections | v1alpha | | | +| legacy.legacyCreateOrUpdateCase | v1alpha | | | +| legacy.legacyCreateSoarAlert | v1alpha | | | +| legacy.legacyFetchAlertsView | v1alpha | chronicle.alert.get_alerts | secops alert | +| legacy.legacyFetchUdmSearchCsv | v1alpha | chronicle.udm_search.fetch_udm_search_csv | secops search --csv | +| legacy.legacyFetchUdmSearchView | v1alpha | chronicle.udm_search.fetch_udm_search_view | secops udm-search-view | +| legacy.legacyFindAssetEvents | v1alpha | | | +| legacy.legacyFindRawLogs | v1alpha | | | +| legacy.legacyFindUdmEvents | v1alpha | | | +| legacy.legacyGetAlert | v1alpha | chronicle.rule_alert.get_alert | | +| legacy.legacyGetCuratedRulesTrends | v1alpha | | | +| legacy.legacyGetDetection | v1alpha | | | +| legacy.legacyGetEventForDetection | v1alpha | | | +| legacy.legacyGetRuleCounts | v1alpha | | | +| legacy.legacyGetRulesTrends | v1alpha | | | +| legacy.legacyListCases | v1alpha | chronicle.case.get_cases | secops case --ids | +| legacy.legacyRunTestRule | v1alpha | chronicle.rule.run_rule_test | secops rule validate | +| legacy.legacySearchArtifactEvents | v1alpha | | | +| legacy.legacySearchArtifactIoCDetails | v1alpha | | | +| legacy.legacySearchAssetEvents | v1alpha | | | +| legacy.legacySearchCuratedDetections | v1alpha | | | +| legacy.legacySearchCustomerStats | v1alpha | | | +| legacy.legacySearchDetections | v1alpha | chronicle.rule_detection.list_detections | | +| legacy.legacySearchDomainsRecentlyRegistered | v1alpha | | | +| legacy.legacySearchDomainsTimingStats | v1alpha | | | +| legacy.legacySearchEnterpriseWideAlerts | v1alpha | | | +| legacy.legacySearchEnterpriseWideIoCs | v1alpha | chronicle.ioc.list_iocs | secops iocs | +| legacy.legacySearchFindings | v1alpha | | | +| legacy.legacySearchIngestionStats | v1alpha | | | +| legacy.legacySearchIoCInsights | v1alpha | | | +| legacy.legacySearchRawLogs | v1alpha | | | +| legacy.legacySearchRuleDetectionCountBuckets | v1alpha | | | +| legacy.legacySearchRuleDetectionEvents | v1alpha | | | +| legacy.legacySearchRuleResults | v1alpha | | | +| legacy.legacySearchRulesAlerts | v1alpha | chronicle.rule_alert.search_rule_alerts | | +| legacy.legacySearchUserEvents | v1alpha | | | +| legacy.legacyStreamDetectionAlerts | v1alpha | | | +| legacy.legacyTestRuleStreaming | v1alpha | | | +| legacy.legacyUpdateAlert | v1alpha | chronicle.rule_alert.update_alert | | +| listAllFindingsRefinementDeployments | v1alpha | | | +| logTypes.create | v1alpha | | | +| logTypes.generateEventTypesSuggestions | v1alpha | | | +| logTypes.get | v1alpha | | | +| logTypes.getLogTypeSetting | v1alpha | | | +| logTypes.legacySubmitParserExtension | v1alpha | | | +| logTypes.list | v1alpha | | | +| logTypes.logs.export | v1alpha | | | +| logTypes.logs.get | v1alpha | | | +| logTypes.logs.import | v1alpha | chronicle.log_ingest.ingest_log | secops log ingest | +| logTypes.logs.list | v1alpha | | | +| logTypes.parserExtensions.activate | v1alpha | chronicle.parser_extension.activate_parser_extension | secops parser-extension activate | +| logTypes.parserExtensions.create | v1alpha | chronicle.parser_extension.create_parser_extension | secops parser-extension create | +| logTypes.parserExtensions.delete | v1alpha | chronicle.parser_extension.delete_parser_extension | secops parser-extension delete | +| logTypes.parserExtensions.extensionValidationReports.get | v1alpha | | | +| logTypes.parserExtensions.extensionValidationReports.list | v1alpha | | | +| logTypes.parserExtensions.extensionValidationReports.validationErrors.list | v1alpha | | | +| logTypes.parserExtensions.get | v1alpha | chronicle.parser_extension.get_parser_extension | secops parser-extension get | +| logTypes.parserExtensions.list | v1alpha | chronicle.parser_extension.list_parser_extensions | secops parser-extension list | +| logTypes.parserExtensions.validationReports.get | v1alpha | | | +| logTypes.parserExtensions.validationReports.parsingErrors.list | v1alpha | | | +| logTypes.parsers.activate | v1alpha | chronicle.parser.activate_parser | secops parser activate | +| logTypes.parsers.activateReleaseCandidateParser | v1alpha | chronicle.parser.activate_release_candidate | secops parser activate-rc | +| logTypes.parsers.copy | v1alpha | chronicle.parser.copy_parser | secops parser copy | +| logTypes.parsers.create | v1alpha | chronicle.parser.create_parser | secops parser create | +| logTypes.parsers.deactivate | v1alpha | chronicle.parser.deactivate_parser | secops parser deactivate | +| logTypes.parsers.delete | v1alpha | chronicle.parser.delete_parser | secops parser delete | +| logTypes.parsers.get | v1alpha | chronicle.parser.get_parser | secops parser get | +| logTypes.parsers.list | v1alpha | chronicle.parser.list_parsers | secops parser list | +| logTypes.parsers.validationReports.get | v1alpha | | | +| logTypes.parsers.validationReports.parsingErrors.list | v1alpha | | | +| logTypes.patch | v1alpha | | | +| logTypes.runParser | v1alpha | chronicle.parser.run_parser | secops parser run | +| logTypes.updateLogTypeSetting | v1alpha | | | +| logProcessingPipelines.associateStreams | v1alpha | chronicle.log_processing_pipelines.associate_streams | secops log-processing associate-streams | +| logProcessingPipelines.create | v1alpha | chronicle.log_processing_pipelines.create_log_processing_pipeline | secops log-processing create | +| logProcessingPipelines.delete | v1alpha | chronicle.log_processing_pipelines.delete_log_processing_pipeline | secops log-processing delete | +| logProcessingPipelines.dissociateStreams | v1alpha | chronicle.log_processing_pipelines.dissociate_streams | secops log-processing dissociate-streams | +| logProcessingPipelines.fetchAssociatedPipeline | v1alpha | chronicle.log_processing_pipelines.fetch_associated_pipeline | secops log-processing fetch-associated | +| logProcessingPipelines.fetchSampleLogsByStreams | v1alpha | chronicle.log_processing_pipelines.fetch_sample_logs_by_streams | secops log-processing fetch-sample-logs | +| logProcessingPipelines.get | v1alpha | chronicle.log_processing_pipelines.get_log_processing_pipeline | secops log-processing get | +| logProcessingPipelines.list | v1alpha | chronicle.log_processing_pipelines.list_log_processing_pipelines | secops log-processing list | +| logProcessingPipelines.patch | v1alpha | chronicle.log_processing_pipelines.update_log_processing_pipeline | secops log-processing update | +| logProcessingPipelines.testPipeline | v1alpha | chronicle.log_processing_pipelines.test_pipeline | secops log-processing test | +| logs.classify | v1alpha | chronicle.log_types.classify_logs | secops log classify | +| nativeDashboards.addChart | v1alpha | chronicle.dashboard.add_chart | secops dashboard add-chart | +| nativeDashboards.create | v1alpha | chronicle.dashboard.create_dashboard | secops dashboard create | +| nativeDashboards.delete | v1alpha | chronicle.dashboard.delete_dashboard | secops dashboard delete | +| nativeDashboards.duplicate | v1alpha | chronicle.dashboard.duplicate_dashboard | secops dashboard duplicate | +| nativeDashboards.duplicateChart | v1alpha | | | +| nativeDashboards.editChart | v1alpha | chronicle.dashboard.edit_chart | secops dashboard edit-chart | +| nativeDashboards.export | v1alpha | chronicle.dashboard.export_dashboard | secops dashboard export | +| nativeDashboards.get | v1alpha | chronicle.dashboard.get_dashboard | secops dashboard get | +| nativeDashboards.import | v1alpha | chronicle.dashboard.import_dashboard | secops dashboard import | +| nativeDashboards.list | v1alpha | chronicle.dashboard.list_dashboards | secops dashboard list | +| nativeDashboards.patch | v1alpha | chronicle.dashboard.update_dashboard | secops dashboard update | +| nativeDashboards.removeChart | v1alpha | chronicle.dashboard.remove_chart | secops dashboard remove-chart | +| operations.cancel | v1alpha | | | +| operations.delete | v1alpha | | | +| operations.get | v1alpha | | | +| operations.list | v1alpha | | | +| operations.streamSearch | v1alpha | | | +| queryProductSourceStats | v1alpha | | | +| referenceLists.create | v1alpha | | | +| referenceLists.get | v1alpha | | | +| referenceLists.list | v1alpha | | | +| referenceLists.patch | v1alpha | | | +| report | v1alpha | | | +| ruleExecutionErrors.list | v1alpha | chronicle.rule_detection.list_errors | | +| rules.create | v1alpha | | | +| rules.delete | v1alpha | | | +| rules.deployments.list | v1alpha | | | +| rules.get | v1alpha | | | +| rules.getDeployment | v1alpha | | | +| rules.list | v1alpha | | | +| rules.listRevisions | v1alpha | | | +| rules.patch | v1alpha | | | +| rules.retrohunts.create | v1alpha | | | +| rules.retrohunts.get | v1alpha | | | +| rules.retrohunts.list | v1alpha | | | +| rules.updateDeployment | v1alpha | | | +| searchEntities | v1alpha | | | +| searchRawLogs | v1alpha | chronicle.log_search.search_raw_logs | secops search raw-logs | +| summarizeEntitiesFromQuery | v1alpha | chronicle.entity.summarize_entity | secops entity | +| summarizeEntity | v1alpha | chronicle.entity.summarize_entity | | +| testFindingsRefinement | v1alpha | | | +| translateUdmQuery | v1alpha | chronicle.nl_search.translate_nl_to_udm | | +| translateYlRule | v1alpha | | | +| udmSearch | v1alpha | chronicle.search.search_udm | secops search | +| undelete | v1alpha | | | +| updateBigQueryExport | v1alpha | | | +| updateRiskConfig | v1alpha | | | +| users.clearConversationHistory | v1alpha | | | +| users.conversations.create | v1alpha | chronicle.gemini.create_conversation | | +| users.conversations.delete | v1alpha | | | +| users.conversations.get | v1alpha | | | +| users.conversations.list | v1alpha | | | +| users.conversations.messages.create | v1alpha | chronicle.gemini.query_gemini | secops gemini | +| users.conversations.messages.delete | v1alpha | | | +| users.conversations.messages.get | v1alpha | | | +| users.conversations.messages.list | v1alpha | | | +| users.conversations.messages.patch | v1alpha | | | +| users.conversations.patch | v1alpha | | | +| users.getPreferenceSet | v1alpha | chronicle.gemini.opt_in_to_gemini | secops gemini --opt-in | +| users.searchQueries.create | v1alpha | | | +| users.searchQueries.delete | v1alpha | | | +| users.searchQueries.get | v1alpha | | | +| users.searchQueries.list | v1alpha | | | +| users.searchQueries.patch | v1alpha | | | +| users.updatePreferenceSet | v1alpha | | | +| validateQuery | v1alpha | chronicle.validate.validate_query | | +| verifyReferenceList | v1alpha | | | +| verifyRuleText | v1alpha | chronicle.rule_validation.validate_rule | secops rule validate | +| watchlists.create | v1alpha | | | +| watchlists.delete | v1alpha | | | +| watchlists.entities.add | v1alpha | | | +| watchlists.entities.batchAdd | v1alpha | | | +| watchlists.entities.batchRemove | v1alpha | | | +| watchlists.entities.remove | v1alpha | | | +| watchlists.get | v1alpha | | | +| watchlists.list | v1alpha | | | +| watchlists.listEntities | v1alpha | | | +| watchlists.patch | v1alpha | | | diff --git a/src/secops/chronicle/__init__.py b/src/secops/chronicle/__init__.py index 7af4dc1c..76d4150f 100644 --- a/src/secops/chronicle/__init__.py +++ b/src/secops/chronicle/__init__.py @@ -111,27 +111,43 @@ search_log_types, ) from secops.chronicle.models import ( + AdvancedConfig, AlertCount, AlertState, Case, CaseList, + DailyScheduleDetails, DataExport, DataExportStage, DataExportStatus, + Date, + DayOfWeek, DetectionType, + DiffType, Entity, EntityMetadata, EntityMetrics, EntitySummary, FileMetadataAndProperties, InputInterval, + IntegrationJobInstanceParameter, + IntegrationParam, + IntegrationParamType, + IntegrationType, ListBasis, + MonthlyScheduleDetails, + OneTimeScheduleDetails, PrevalenceData, + PythonVersion, + ScheduleType, SoarPlatformInfo, + TargetMode, TileType, TimeInterval, Timeline, TimelineBucket, + TimeOfDay, + WeeklyScheduleDetails, WidgetMetadata, ) from secops.chronicle.nl_search import translate_nl_to_udm @@ -340,21 +356,31 @@ "execute_query", "get_execute_query", # Models + "AdvancedConfig", + "AlertCount", + "AlertState", + "Case", + "CaseList", + "DailyScheduleDetails", + "Date", + "DayOfWeek", "Entity", "EntityMetadata", "EntityMetrics", + "EntitySummary", + "FileMetadataAndProperties", + "IntegrationJobInstanceParameter", + "MonthlyScheduleDetails", + "OneTimeScheduleDetails", + "PrevalenceData", + "ScheduleType", + "SoarPlatformInfo", "TimeInterval", - "TimelineBucket", "Timeline", + "TimelineBucket", + "TimeOfDay", + "WeeklyScheduleDetails", "WidgetMetadata", - "EntitySummary", - "AlertCount", - "AlertState", - "Case", - "SoarPlatformInfo", - "CaseList", - "PrevalenceData", - "FileMetadataAndProperties", "ValidationResult", "GeminiResponse", "Block", diff --git a/src/secops/chronicle/client.py b/src/secops/chronicle/client.py index 949a5565..da7c9311 100644 --- a/src/secops/chronicle/client.py +++ b/src/secops/chronicle/client.py @@ -13,6 +13,7 @@ # limitations under the License. # """Chronicle API client.""" + import ipaddress import re from collections.abc import Iterator @@ -22,154 +23,125 @@ from google.auth.transport import requests as google_auth_requests +# pylint: disable=line-too-long from secops import auth as secops_auth from secops.auth import RetryConfig +from secops.chronicle.soar import SOARService from secops.chronicle.alert import get_alerts as _get_alerts -from secops.chronicle.case import execute_bulk_add_tag as _execute_bulk_add_tag -from secops.chronicle.case import execute_bulk_assign as _execute_bulk_assign from secops.chronicle.case import ( + execute_bulk_add_tag as _execute_bulk_add_tag, + execute_bulk_assign as _execute_bulk_assign, execute_bulk_change_priority as _execute_bulk_change_priority, -) -from secops.chronicle.case import ( execute_bulk_change_stage as _execute_bulk_change_stage, + execute_bulk_close as _execute_bulk_close, + execute_bulk_reopen as _execute_bulk_reopen, + get_case as _get_case, + get_cases_from_list, + list_cases as _list_cases, + merge_cases as _merge_cases, + patch_case as _patch_case, ) -from secops.chronicle.case import execute_bulk_close as _execute_bulk_close -from secops.chronicle.case import execute_bulk_reopen as _execute_bulk_reopen -from secops.chronicle.case import get_case as _get_case -from secops.chronicle.case import get_cases_from_list -from secops.chronicle.case import list_cases as _list_cases -from secops.chronicle.case import merge_cases as _merge_cases -from secops.chronicle.case import patch_case as _patch_case -from secops.chronicle.dashboard import DashboardAccessType, DashboardView -from secops.chronicle.dashboard import add_chart as _add_chart -from secops.chronicle.dashboard import create_dashboard as _create_dashboard -from secops.chronicle.dashboard import delete_dashboard as _delete_dashboard from secops.chronicle.dashboard import ( + DashboardAccessType, + DashboardView, + add_chart as _add_chart, + create_dashboard as _create_dashboard, + delete_dashboard as _delete_dashboard, duplicate_dashboard as _duplicate_dashboard, + edit_chart as _edit_chart, + export_dashboard as _export_dashboard, + get_chart as _get_chart, + get_dashboard as _get_dashboard, + import_dashboard as _import_dashboard, + list_dashboards as _list_dashboards, + remove_chart as _remove_chart, + update_dashboard as _update_dashboard, ) -from secops.chronicle.dashboard import edit_chart as _edit_chart -from secops.chronicle.dashboard import export_dashboard as _export_dashboard -from secops.chronicle.dashboard import get_chart as _get_chart -from secops.chronicle.dashboard import get_dashboard as _get_dashboard -from secops.chronicle.dashboard import import_dashboard as _import_dashboard -from secops.chronicle.dashboard import list_dashboards as _list_dashboards -from secops.chronicle.dashboard import remove_chart as _remove_chart -from secops.chronicle.dashboard import update_dashboard as _update_dashboard from secops.chronicle.dashboard_query import ( execute_query as _execute_dashboard_query, -) -from secops.chronicle.dashboard_query import ( get_execute_query as _get_execute_query, ) from secops.chronicle.data_export import ( cancel_data_export as _cancel_data_export, -) -from secops.chronicle.data_export import ( create_data_export as _create_data_export, -) -from secops.chronicle.data_export import ( fetch_available_log_types as _fetch_available_log_types, -) -from secops.chronicle.data_export import get_data_export as _get_data_export -from secops.chronicle.data_export import list_data_export as _list_data_export -from secops.chronicle.data_export import ( + get_data_export as _get_data_export, + list_data_export as _list_data_export, update_data_export as _update_data_export, ) -from secops.chronicle.data_table import DataTableColumnType -from secops.chronicle.data_table import create_data_table as _create_data_table from secops.chronicle.data_table import ( + DataTableColumnType, + create_data_table as _create_data_table, create_data_table_rows as _create_data_table_rows, -) -from secops.chronicle.data_table import delete_data_table as _delete_data_table -from secops.chronicle.data_table import ( + delete_data_table as _delete_data_table, delete_data_table_rows as _delete_data_table_rows, -) -from secops.chronicle.data_table import get_data_table as _get_data_table -from secops.chronicle.data_table import ( + get_data_table as _get_data_table, list_data_table_rows as _list_data_table_rows, -) -from secops.chronicle.data_table import list_data_tables as _list_data_tables -from secops.chronicle.data_table import ( + list_data_tables as _list_data_tables, replace_data_table_rows as _replace_data_table_rows, -) -from secops.chronicle.data_table import update_data_table as _update_data_table -from secops.chronicle.data_table import ( + update_data_table as _update_data_table, update_data_table_rows as _update_data_table_rows, ) -from secops.chronicle.entity import _detect_value_type_for_query -from secops.chronicle.entity import summarize_entity as _summarize_entity -from secops.chronicle.feeds import CreateFeedModel, UpdateFeedModel -from secops.chronicle.feeds import create_feed as _create_feed -from secops.chronicle.feeds import delete_feed as _delete_feed -from secops.chronicle.feeds import disable_feed as _disable_feed -from secops.chronicle.feeds import enable_feed as _enable_feed -from secops.chronicle.feeds import generate_secret as _generate_secret -from secops.chronicle.feeds import get_feed as _get_feed -from secops.chronicle.feeds import list_feeds as _list_feeds -from secops.chronicle.feeds import update_feed as _update_feed -from secops.chronicle.gemini import GeminiResponse -from secops.chronicle.gemini import opt_in_to_gemini as _opt_in_to_gemini -from secops.chronicle.gemini import query_gemini as _query_gemini -from secops.chronicle.ioc import list_iocs as _list_iocs -from secops.chronicle.investigations import ( - fetch_associated_investigations as _fetch_associated_investigations, +from secops.chronicle.entity import ( + _detect_value_type_for_query, + summarize_entity as _summarize_entity, ) -from secops.chronicle.investigations import ( - get_investigation as _get_investigation, +from secops.chronicle.featured_content_rules import ( + list_featured_content_rules as _list_featured_content_rules, ) -from secops.chronicle.investigations import ( - list_investigations as _list_investigations, +from secops.chronicle.feeds import ( + CreateFeedModel, + UpdateFeedModel, + create_feed as _create_feed, + delete_feed as _delete_feed, + disable_feed as _disable_feed, + enable_feed as _enable_feed, + generate_secret as _generate_secret, + get_feed as _get_feed, + list_feeds as _list_feeds, + update_feed as _update_feed, +) +from secops.chronicle.gemini import ( + GeminiResponse, + opt_in_to_gemini as _opt_in_to_gemini, + query_gemini as _query_gemini, ) from secops.chronicle.investigations import ( + fetch_associated_investigations as _fetch_associated_investigations, + get_investigation as _get_investigation, + list_investigations as _list_investigations, trigger_investigation as _trigger_investigation, ) -from secops.chronicle.log_ingest import create_forwarder as _create_forwarder -from secops.chronicle.log_ingest import delete_forwarder as _delete_forwarder -from secops.chronicle.log_ingest import get_forwarder as _get_forwarder +from secops.chronicle.ioc import list_iocs as _list_iocs from secops.chronicle.log_ingest import ( + create_forwarder as _create_forwarder, + delete_forwarder as _delete_forwarder, + get_forwarder as _get_forwarder, get_or_create_forwarder as _get_or_create_forwarder, + import_entities as _import_entities, + ingest_log as _ingest_log, + ingest_udm as _ingest_udm, + list_forwarders as _list_forwarders, + update_forwarder as _update_forwarder, ) -from secops.chronicle.log_ingest import import_entities as _import_entities -from secops.chronicle.log_ingest import ingest_log as _ingest_log -from secops.chronicle.log_ingest import ingest_udm as _ingest_udm -from secops.chronicle.log_ingest import list_forwarders as _list_forwarders -from secops.chronicle.log_ingest import update_forwarder as _update_forwarder -from secops.chronicle.log_types import classify_logs as _classify_logs -from secops.chronicle.log_types import get_all_log_types as _get_all_log_types -from secops.chronicle.log_types import ( - get_log_type_description as _get_log_type_description, -) -from secops.chronicle.log_types import is_valid_log_type as _is_valid_log_type -from secops.chronicle.log_types import search_log_types as _search_log_types from secops.chronicle.log_processing_pipelines import ( associate_streams as _associate_streams, -) -from secops.chronicle.log_processing_pipelines import ( create_log_processing_pipeline as _create_log_processing_pipeline, -) -from secops.chronicle.log_processing_pipelines import ( delete_log_processing_pipeline as _delete_log_processing_pipeline, -) -from secops.chronicle.log_processing_pipelines import ( dissociate_streams as _dissociate_streams, -) -from secops.chronicle.log_processing_pipelines import ( fetch_associated_pipeline as _fetch_associated_pipeline, -) -from secops.chronicle.log_processing_pipelines import ( fetch_sample_logs_by_streams as _fetch_sample_logs_by_streams, -) -from secops.chronicle.log_processing_pipelines import ( get_log_processing_pipeline as _get_log_processing_pipeline, -) -from secops.chronicle.log_processing_pipelines import ( list_log_processing_pipelines as _list_log_processing_pipelines, -) -from secops.chronicle.log_processing_pipelines import ( + test_pipeline as _test_pipeline, update_log_processing_pipeline as _update_log_processing_pipeline, ) -from secops.chronicle.log_processing_pipelines import ( - test_pipeline as _test_pipeline, +from secops.chronicle.log_types import ( + classify_logs as _classify_logs, + get_all_log_types as _get_all_log_types, + get_log_type_description as _get_log_type_description, + is_valid_log_type as _is_valid_log_type, + search_log_types as _search_log_types, ) from secops.chronicle.models import ( APIVersion, @@ -182,102 +154,70 @@ InputInterval, TileType, ) -from secops.chronicle.nl_search import nl_search as _nl_search -from secops.chronicle.nl_search import translate_nl_to_udm -from secops.chronicle.parser import activate_parser as _activate_parser +from secops.chronicle.nl_search import ( + nl_search as _nl_search, + translate_nl_to_udm, +) from secops.chronicle.parser import ( + activate_parser as _activate_parser, activate_release_candidate_parser as _activate_release_candidate_parser, + copy_parser as _copy_parser, + create_parser as _create_parser, + deactivate_parser as _deactivate_parser, + delete_parser as _delete_parser, + get_parser as _get_parser, + list_parsers as _list_parsers, + run_parser as _run_parser, ) -from secops.chronicle.parser import copy_parser as _copy_parser -from secops.chronicle.parser import create_parser as _create_parser -from secops.chronicle.parser import deactivate_parser as _deactivate_parser -from secops.chronicle.parser import delete_parser as _delete_parser -from secops.chronicle.parser import get_parser as _get_parser -from secops.chronicle.parser import list_parsers as _list_parsers -from secops.chronicle.parser import run_parser as _run_parser -from secops.chronicle.parser_extension import ParserExtensionConfig from secops.chronicle.parser_extension import ( + ParserExtensionConfig, activate_parser_extension as _activate_parser_extension, -) -from secops.chronicle.parser_extension import ( create_parser_extension as _create_parser_extension, -) -from secops.chronicle.parser_extension import ( delete_parser_extension as _delete_parser_extension, -) -from secops.chronicle.parser_extension import ( get_parser_extension as _get_parser_extension, -) -from secops.chronicle.parser_extension import ( list_parser_extensions as _list_parser_extensions, ) from secops.chronicle.reference_list import ( ReferenceListSyntaxType, ReferenceListView, -) -from secops.chronicle.reference_list import ( create_reference_list as _create_reference_list, -) -from secops.chronicle.reference_list import ( get_reference_list as _get_reference_list, -) -from secops.chronicle.reference_list import ( list_reference_lists as _list_reference_lists, -) -from secops.chronicle.reference_list import ( update_reference_list as _update_reference_list, ) - -# Import rule functions -from secops.chronicle.rule import create_rule as _create_rule -from secops.chronicle.rule import delete_rule as _delete_rule -from secops.chronicle.rule import enable_rule as _enable_rule -from secops.chronicle.rule import get_rule as _get_rule -from secops.chronicle.rule import get_rule_deployment as _get_rule_deployment from secops.chronicle.rule import ( + create_rule as _create_rule, + delete_rule as _delete_rule, + enable_rule as _enable_rule, + get_rule as _get_rule, + get_rule_deployment as _get_rule_deployment, list_rule_deployments as _list_rule_deployments, -) -from secops.chronicle.rule import list_rules as _list_rules -from secops.chronicle.rule import run_rule_test -from secops.chronicle.rule import search_rules as _search_rules -from secops.chronicle.rule import set_rule_alerting as _set_rule_alerting -from secops.chronicle.rule import update_rule as _update_rule -from secops.chronicle.rule import ( + list_rules as _list_rules, + run_rule_test, + search_rules as _search_rules, + set_rule_alerting as _set_rule_alerting, + update_rule as _update_rule, update_rule_deployment as _update_rule_deployment, ) from secops.chronicle.rule_alert import ( bulk_update_alerts as _bulk_update_alerts, -) -from secops.chronicle.rule_alert import get_alert as _get_alert -from secops.chronicle.rule_alert import ( + get_alert as _get_alert, search_rule_alerts as _search_rule_alerts, + update_alert as _update_alert, +) +from secops.chronicle.rule_detection import ( + list_detections as _list_detections, + list_errors as _list_errors, ) -from secops.chronicle.rule_alert import update_alert as _update_alert -from secops.chronicle.rule_detection import list_detections as _list_detections -from secops.chronicle.rule_detection import list_errors as _list_errors from secops.chronicle.rule_exclusion import ( RuleExclusionType, UpdateRuleDeployment, -) -from secops.chronicle.rule_exclusion import ( compute_rule_exclusion_activity as _compute_rule_exclusion_activity, -) -from secops.chronicle.rule_exclusion import ( create_rule_exclusion as _create_rule_exclusion, -) -from secops.chronicle.rule_exclusion import ( get_rule_exclusion as _get_rule_exclusion, -) -from secops.chronicle.rule_exclusion import ( get_rule_exclusion_deployment as _get_rule_exclusion_deployment, -) -from secops.chronicle.rule_exclusion import ( list_rule_exclusions as _list_rule_exclusions, -) -from secops.chronicle.rule_exclusion import ( patch_rule_exclusion as _patch_rule_exclusion, -) -from secops.chronicle.rule_exclusion import ( update_rule_exclusion_deployment as _update_rule_exclusion_deployment, ) from secops.chronicle.rule_retrohunt import ( @@ -286,72 +226,45 @@ list_retrohunts as _list_retrohunts, ) from secops.chronicle.rule_set import ( - batch_update_curated_rule_set_deployments as _batch_update_curated_rule_set_deployments, # pylint: disable=line-too-long -) -from secops.chronicle.rule_set import get_curated_rule as _get_curated_rule -from secops.chronicle.rule_set import ( + batch_update_curated_rule_set_deployments as _batch_update_curated_rule_set_deployments, + get_curated_rule as _get_curated_rule, get_curated_rule_by_name as _get_curated_rule_by_name, -) -from secops.chronicle.rule_set import ( get_curated_rule_set as _get_curated_rule_set, -) -from secops.chronicle.rule_set import ( get_curated_rule_set_category as _get_curated_rule_set_category, -) -from secops.chronicle.rule_set import ( get_curated_rule_set_deployment as _get_curated_rule_set_deployment, -) -from secops.chronicle.rule_set import ( - get_curated_rule_set_deployment_by_name as _get_curated_rule_set_deployment_by_name, # pylint: disable=line-too-long -) -from secops.chronicle.rule_set import ( + get_curated_rule_set_deployment_by_name as _get_curated_rule_set_deployment_by_name, list_curated_rule_set_categories as _list_curated_rule_set_categories, -) -from secops.chronicle.rule_set import ( list_curated_rule_set_deployments as _list_curated_rule_set_deployments, -) -from secops.chronicle.rule_set import ( list_curated_rule_sets as _list_curated_rule_sets, -) -from secops.chronicle.rule_set import list_curated_rules as _list_curated_rules -from secops.chronicle.rule_set import ( + list_curated_rules as _list_curated_rules, search_curated_detections as _search_curated_detections, -) -from secops.chronicle.rule_set import ( update_curated_rule_set_deployment as _update_curated_rule_set_deployment, ) -from secops.chronicle.featured_content_rules import ( - list_featured_content_rules as _list_featured_content_rules, -) from secops.chronicle.rule_validation import validate_rule as _validate_rule from secops.chronicle.search import search_udm as _search_udm from secops.chronicle.log_search import search_raw_logs as _search_raw_logs from secops.chronicle.stats import get_stats as _get_stats -from secops.chronicle.udm_mapping import RowLogFormat from secops.chronicle.udm_mapping import ( + RowLogFormat, generate_udm_key_value_mappings as _generate_udm_key_value_mappings, ) - -# Import functions from the new modules from secops.chronicle.udm_search import ( fetch_udm_search_csv as _fetch_udm_search_csv, -) -from secops.chronicle.udm_search import ( fetch_udm_search_view as _fetch_udm_search_view, -) -from secops.chronicle.udm_search import ( find_udm_field_values as _find_udm_field_values, ) from secops.chronicle.validate import validate_query as _validate_query from secops.chronicle.watchlist import ( - list_watchlists as _list_watchlists, - get_watchlist as _get_watchlist, - delete_watchlist as _delete_watchlist, create_watchlist as _create_watchlist, + delete_watchlist as _delete_watchlist, + get_watchlist as _get_watchlist, + list_watchlists as _list_watchlists, update_watchlist as _update_watchlist, ) from secops.exceptions import SecOpsError +# pylint: enable=line-too-long + class ValueType(Enum): """Chronicle API value types.""" @@ -513,6 +426,7 @@ def __init__( self.default_api_version = APIVersion(default_api_version) self._default_forwarder_display_name: str = "Wrapper-SDK-Forwarder" self._cached_default_forwarder_id: str | None = None + self.soar = SOARService(self) # Format the instance ID to match the expected format if region in ["dev", "staging"]: diff --git a/src/secops/chronicle/entity.py b/src/secops/chronicle/entity.py index 429d4393..84e5060a 100644 --- a/src/secops/chronicle/entity.py +++ b/src/secops/chronicle/entity.py @@ -15,6 +15,7 @@ """ Provides entity search, analysis and summarization functionality for Chronicle. """ + import ipaddress import re from datetime import datetime diff --git a/src/secops/chronicle/feeds.py b/src/secops/chronicle/feeds.py index b9ed7f22..8030b753 100644 --- a/src/secops/chronicle/feeds.py +++ b/src/secops/chronicle/feeds.py @@ -15,6 +15,7 @@ """ Provides ingestion feed management functionality for Chronicle. """ + import json import os import sys diff --git a/src/secops/chronicle/gemini.py b/src/secops/chronicle/gemini.py index abed52cb..eee42374 100644 --- a/src/secops/chronicle/gemini.py +++ b/src/secops/chronicle/gemini.py @@ -16,6 +16,7 @@ Provides access to Chronicle's Gemini conversational AI interface. """ + import re from typing import Any diff --git a/src/secops/chronicle/models.py b/src/secops/chronicle/models.py index 5db56d27..82451f58 100644 --- a/src/secops/chronicle/models.py +++ b/src/secops/chronicle/models.py @@ -13,6 +13,7 @@ # limitations under the License. # """Data models for Chronicle API responses.""" + import json import sys from dataclasses import asdict, dataclass, field @@ -73,6 +74,686 @@ class DetectionType(StrEnum): CASE = "DETECTION_TYPE_CASE" +class PythonVersion(str, Enum): + """Python version for compatibility checks.""" + + UNSPECIFIED = "PYTHON_VERSION_UNSPECIFIED" + PYTHON_2_7 = "V2_7" + PYTHON_3_7 = "V3_7" + PYTHON_3_11 = "V3_11" + + +class DiffType(str, Enum): + """Type of diff to retrieve.""" + + COMMERCIAL = "Commercial" + PRODUCTION = "Production" + STAGING = "Staging" + + +class TargetMode(str, Enum): + """Target mode for integration transition.""" + + PRODUCTION = "Production" + STAGING = "Staging" + + +class IntegrationType(str, Enum): + """Type of integration.""" + + UNSPECIFIED = "INTEGRATION_TYPE_UNSPECIFIED" + RESPONSE = "RESPONSE" + EXTENSION = "EXTENSION" + + +class IntegrationParamType(str, Enum): + """Type of integration parameter.""" + + PARAM_TYPE_UNSPECIFIED = "PARAM_TYPE_UNSPECIFIED" + BOOLEAN = "BOOLEAN" + INT = "INT" + STRING = "STRING" + PASSWORD = "PASSWORD" + IP = "IP" + IP_OR_HOST = "IP_OR_HOST" + URL = "URL" + DOMAIN = "DOMAIN" + EMAIL = "EMAIL" + VALUES_LIST = "VALUES_LIST" + VALUES_AS_SEMICOLON_SEPARATED_STRING = ( + "VALUES_AS_SEMICOLON_SEPARATED_STRING" + ) + MULTI_VALUES_SELECTION = "MULTI_VALUES_SELECTION" + SCRIPT = "SCRIPT" + FILTER_LIST = "FILTER_LIST" + + +@dataclass +class IntegrationParam: + """A parameter definition for a Chronicle SOAR integration. + + Attributes: + display_name: Human-readable label shown in the UI. + property_name: The programmatic key used in code/config. + type: The data type of the parameter (see IntegrationParamType). + description: Optional. Explanation of what the parameter is for. + mandatory: Whether the parameter must be supplied. Defaults to False. + default_value: Optional. Pre-filled value shown in the UI. + """ + + display_name: str + property_name: str + type: IntegrationParamType + mandatory: bool + description: str | None = None + default_value: str | None = None + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + data: dict = { + "displayName": self.display_name, + "propertyName": self.property_name, + "type": str(self.type.value), + "mandatory": self.mandatory, + } + if self.description is not None: + data["description"] = self.description + if self.default_value is not None: + data["defaultValue"] = self.default_value + return data + + +class ActionParamType(str, Enum): + """Action parameter types for Chronicle SOAR integration actions.""" + + STRING = "STRING" + BOOLEAN = "BOOLEAN" + WFS_REPOSITORY = "WFS_REPOSITORY" + USER_REPOSITORY = "USER_REPOSITORY" + STAGES_REPOSITORY = "STAGES_REPOSITORY" + CLOSE_CASE_REASON_REPOSITORY = "CLOSE_CASE_REASON_REPOSITORY" + CLOSE_CASE_ROOT_CAUSE_REPOSITORY = "CLOSE_CASE_ROOT_CAUSE_REPOSITORY" + PRIORITIES_REPOSITORY = "PRIORITIES_REPOSITORY" + EMAIL_CONTENT = "EMAIL_CONTENT" + CONTENT = "CONTENT" + PASSWORD = "PASSWORD" + ENTITY_TYPE = "ENTITY_TYPE" + MULTI_VALUES = "MULTI_VALUES" + LIST = "LIST" + CODE = "CODE" + MULTIPLE_CHOICE_PARAMETER = "MULTIPLE_CHOICE_PARAMETER" + + +class ActionType(str, Enum): + """Action types for Chronicle SOAR integration actions.""" + + UNSPECIFIED = "ACTION_TYPE_UNSPECIFIED" + STANDARD = "STANDARD" + AI_AGENT = "AI_AGENT" + + +@dataclass +class ActionParameter: + """A parameter definition for a Chronicle SOAR integration action. + + Attributes: + display_name: The parameter's display name. Maximum 150 characters. + type: The parameter's type. + description: The parameter's description. Maximum 150 characters. + mandatory: Whether the parameter is mandatory. + default_value: The default value of the parameter. + Maximum 150 characters. + optional_values: Parameter's optional values. Maximum 50 items. + """ + + display_name: str + type: ActionParamType + description: str + mandatory: bool + default_value: str | None = None + optional_values: list[str] | None = None + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + data: dict = { + "displayName": self.display_name, + "type": str(self.type.value), + "description": self.description, + "mandatory": self.mandatory, + } + if self.default_value is not None: + data["defaultValue"] = self.default_value + if self.optional_values is not None: + data["optionalValues"] = self.optional_values + return data + + +class ParamType(str, Enum): + """Parameter types for Chronicle SOAR integration functions.""" + + UNSPECIFIED = "PARAM_TYPE_UNSPECIFIED" + BOOLEAN = "BOOLEAN" + INT = "INT" + STRING = "STRING" + PASSWORD = "PASSWORD" + IP = "IP" + IP_OR_HOST = "IP_OR_HOST" + URL = "URL" + DOMAIN = "DOMAIN" + EMAIL = "EMAIL" + VALUES_LIST = "VALUES_LIST" + VALUES_AS_SEMICOLON_SEPARATED_STRING = ( + "VALUES_AS_SEMICOLON_SEPARATED_STRING" + ) + MULTI_VALUES_SELECTION = "MULTI_VALUES_SELECTION" + SCRIPT = "SCRIPT" + FILTER_LIST = "FILTER_LIST" + NUMERICAL_VALUES = "NUMERICAL_VALUES" + + +class ConnectorParamMode(str, Enum): + """Parameter modes for Chronicle SOAR integration connectors.""" + + UNSPECIFIED = "PARAM_MODE_UNSPECIFIED" + REGULAR = "REGULAR" + CONNECTIVITY = "CONNECTIVITY" + SCRIPT = "SCRIPT" + + +class ConnectorRuleType(str, Enum): + """Rule types for Chronicle SOAR integration connectors.""" + + UNSPECIFIED = "RULE_TYPE_UNSPECIFIED" + ALLOW_LIST = "ALLOW_LIST" + BLOCK_LIST = "BLOCK_LIST" + + +@dataclass +class ConnectorParameter: + """A parameter definition for a Chronicle SOAR integration connector. + + Attributes: + display_name: The parameter's display name. + type: The parameter's type. + mode: The parameter's mode. + mandatory: Whether the parameter is mandatory for configuring a + connector instance. + default_value: The default value of the parameter. Required for + boolean and mandatory parameters. + description: The parameter's description. + advanced: The parameter's advanced flag. + """ + + display_name: str + type: ParamType + mode: ConnectorParamMode + mandatory: bool + default_value: str | None = None + description: str | None = None + advanced: bool | None = None + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + data: dict = { + "displayName": self.display_name, + "type": str(self.type.value), + "mode": str(self.mode.value), + "mandatory": self.mandatory, + } + if self.default_value is not None: + data["defaultValue"] = self.default_value + if self.description is not None: + data["description"] = self.description + if self.advanced is not None: + data["advanced"] = self.advanced + return data + + +@dataclass +class IntegrationJobInstanceParameter: + """A parameter instance for a Chronicle SOAR integration job instance. + + Note: Most fields are output-only and will be populated by the API. + Only value needs to be provided when configuring a job instance. + + Attributes: + value: The value of the parameter. + """ + + value: str | None = None + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + data: dict = {} + if self.value is not None: + data["value"] = self.value + return data + + +class ScheduleType(str, Enum): + """Schedule types for Chronicle SOAR integration job + instance advanced config.""" + + UNSPECIFIED = "SCHEDULE_TYPE_UNSPECIFIED" + ONCE = "ONCE" + DAILY = "DAILY" + WEEKLY = "WEEKLY" + MONTHLY = "MONTHLY" + + +class DayOfWeek(str, Enum): + """Days of the week for Chronicle SOAR weekly schedule details.""" + + UNSPECIFIED = "DAY_OF_WEEK_UNSPECIFIED" + MONDAY = "MONDAY" + TUESDAY = "TUESDAY" + WEDNESDAY = "WEDNESDAY" + THURSDAY = "THURSDAY" + FRIDAY = "FRIDAY" + SATURDAY = "SATURDAY" + SUNDAY = "SUNDAY" + + +@dataclass +class Date: + """A calendar date for Chronicle SOAR schedule details. + + Attributes: + year: The year. + month: The month of the year (1-12). + day: The day of the month (1-31). + """ + + year: int + month: int + day: int + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + return {"year": self.year, "month": self.month, "day": self.day} + + +@dataclass +class TimeOfDay: + """A time of day for Chronicle SOAR schedule details. + + Attributes: + hours: The hour of the day (0-23). + minutes: The minute of the hour (0-59). + seconds: The second of the minute (0-59). + nanos: The nanoseconds of the second (0-999999999). + """ + + hours: int + minutes: int + seconds: int = 0 + nanos: int = 0 + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + return { + "hours": self.hours, + "minutes": self.minutes, + "seconds": self.seconds, + "nanos": self.nanos, + } + + +@dataclass +class OneTimeScheduleDetails: + """One-time schedule details for a Chronicle SOAR job instance. + + Attributes: + start_date: The date to run the job. + time: The time to run the job. + """ + + start_date: Date + time: TimeOfDay + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + return { + "startDate": self.start_date.to_dict(), + "time": self.time.to_dict(), + } + + +@dataclass +class DailyScheduleDetails: + """Daily schedule details for a Chronicle SOAR job instance. + + Attributes: + start_date: The start date. + time: The time to run the job. + interval: The day interval. + """ + + start_date: Date + time: TimeOfDay + interval: int + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + return { + "startDate": self.start_date.to_dict(), + "time": self.time.to_dict(), + "interval": self.interval, + } + + +@dataclass +class WeeklyScheduleDetails: + """Weekly schedule details for a Chronicle SOAR job instance. + + Attributes: + start_date: The start date. + days: The days of the week to run the job. + time: The time to run the job. + interval: The week interval. + """ + + start_date: Date + days: list[DayOfWeek] + time: TimeOfDay + interval: int + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + return { + "startDate": self.start_date.to_dict(), + "days": [d.value for d in self.days], + "time": self.time.to_dict(), + "interval": self.interval, + } + + +@dataclass +class MonthlyScheduleDetails: + """Monthly schedule details for a Chronicle SOAR job instance. + + Attributes: + start_date: The start date. + day: The day of the month to run the job. + time: The time to run the job. + interval: The month interval. + """ + + start_date: Date + day: int + time: TimeOfDay + interval: int + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + return { + "startDate": self.start_date.to_dict(), + "day": self.day, + "time": self.time.to_dict(), + "interval": self.interval, + } + + +@dataclass +class AdvancedConfig: + """Advanced scheduling configuration for a Chronicle SOAR job instance. + + Exactly one of the schedule detail fields should be provided, corresponding + to the schedule_type. + + Attributes: + time_zone: The zone id. + schedule_type: The schedule type. + one_time_schedule: One-time schedule details. Use with ONCE. + daily_schedule: Daily schedule details. Use with DAILY. + weekly_schedule: Weekly schedule details. Use with WEEKLY. + monthly_schedule: Monthly schedule details. Use with MONTHLY. + """ + + time_zone: str + schedule_type: ScheduleType + one_time_schedule: OneTimeScheduleDetails | None = None + daily_schedule: DailyScheduleDetails | None = None + weekly_schedule: WeeklyScheduleDetails | None = None + monthly_schedule: MonthlyScheduleDetails | None = None + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + data: dict = { + "timeZone": self.time_zone, + "scheduleType": str(self.schedule_type.value), + } + if self.one_time_schedule is not None: + data["oneTimeSchedule"] = self.one_time_schedule.to_dict() + if self.daily_schedule is not None: + data["dailySchedule"] = self.daily_schedule.to_dict() + if self.weekly_schedule is not None: + data["weeklySchedule"] = self.weekly_schedule.to_dict() + if self.monthly_schedule is not None: + data["monthlySchedule"] = self.monthly_schedule.to_dict() + return data + + +@dataclass +class JobParameter: + """A parameter definition for a Chronicle SOAR integration job. + + Attributes: + id: The parameter's id. + display_name: The parameter's display name. + description: The parameter's description. + mandatory: Whether the parameter is mandatory. + type: The parameter's type. + default_value: The default value of the parameter. + """ + + id: int + display_name: str + description: str + mandatory: bool + type: ParamType + default_value: str | None = None + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + data: dict = { + "id": self.id, + "displayName": self.display_name, + "description": self.description, + "mandatory": self.mandatory, + "type": str(self.type.value), + } + if self.default_value is not None: + data["defaultValue"] = self.default_value + return data + + +class IntegrationParameterType(str, Enum): + """Parameter types for Chronicle SOAR integration instances.""" + + UNSPECIFIED = "INTEGRATION_PARAMETER_TYPE_UNSPECIFIED" + BOOLEAN = "BOOLEAN" + INT = "INT" + STRING = "STRING" + PASSWORD = "PASSWORD" + IP = "IP" + IP_OR_HOST = "IP_OR_HOST" + URL = "URL" + DOMAIN = "DOMAIN" + EMAIL = "EMAIL" + VALUES_LIST = "VALUES_LIST" + VALUES_AS_SEMICOLON_SEPARATED_STRING = ( + "VALUES_AS_SEMICOLON_SEPARATED_STRING" + ) + MULTI_VALUES_SELECTION = "MULTI_VALUES_SELECTION" + SCRIPT = "SCRIPT" + FILTER_LIST = "FILTER_LIST" + + +@dataclass +class IntegrationInstanceParameter: + """A parameter instance for a Chronicle SOAR integration instance. + + Note: Most fields are output-only and will be populated by the API. + Only value needs to be provided when configuring an integration instance. + + Attributes: + value: The parameter's value. + """ + + value: str | None = None + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + data: dict = {} + if self.value is not None: + data["value"] = self.value + return data + + +class ConnectorConnectivityStatus(str, Enum): + """Connectivity status for Chronicle SOAR connector instances.""" + + LIVE = "LIVE" + NOT_LIVE = "NOT_LIVE" + + +@dataclass +class ConnectorInstanceParameter: + """A parameter instance for a Chronicle SOAR connector instance. + + Note: Most fields are output-only and will be populated by the API. + Only value needs to be provided when configuring a connector instance. + + Attributes: + value: The value of the parameter. + """ + + value: str | None = None + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + data: dict = {} + if self.value is not None: + data["value"] = self.value + return data + + +class TransformerType(str, Enum): + """Transformer types for Chronicle SOAR integration transformers.""" + + UNSPECIFIED = "TRANSFORMER_TYPE_UNSPECIFIED" + BUILT_IN = "BUILT_IN" + CUSTOM = "CUSTOM" + + +@dataclass +class TransformerDefinitionParameter: + """A parameter definition for a Chronicle SOAR transformer definition. + + Attributes: + display_name: The parameter's display name. May contain letters, + numbers, and underscores. Maximum 150 characters. + mandatory: Whether the parameter is mandatory for configuring a + transformer instance. + id: The parameter's id. Server-generated on creation; must be + provided when updating an existing parameter. + default_value: The default value of the parameter. Required for + boolean and mandatory parameters. + description: The parameter's description. Maximum 2050 characters. + """ + + display_name: str + mandatory: bool + id: str | None = None + default_value: str | None = None + description: str | None = None + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + data: dict = { + "displayName": self.display_name, + "mandatory": self.mandatory, + } + if self.id is not None: + data["id"] = self.id + if self.default_value is not None: + data["defaultValue"] = self.default_value + if self.description is not None: + data["description"] = self.description + return data + + +class LogicalOperatorType(str, Enum): + """Logical operator types for Chronicle SOAR + integration logical operators.""" + + UNSPECIFIED = "LOGICAL_OPERATOR_TYPE_UNSPECIFIED" + BUILT_IN = "BUILT_IN" + CUSTOM = "CUSTOM" + + +@dataclass +class IntegrationLogicalOperatorParameter: + """A parameter definition for a Chronicle SOAR logical operator. + + Attributes: + display_name: The parameter's display name. May contain letters, + numbers, and underscores. Maximum 150 characters. + mandatory: Whether the parameter is mandatory for configuring a + logical operator instance. + id: The parameter's id. Server-generated on creation; must be + provided when updating an existing parameter. + default_value: The default value of the parameter. Required for + boolean and mandatory parameters. + order: The parameter's order in the parameters list. + description: The parameter's description. Maximum 2050 characters. + """ + + display_name: str + mandatory: bool + id: str | None = None + default_value: str | None = None + order: int | None = None + description: str | None = None + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + data: dict = { + "displayName": self.display_name, + "mandatory": self.mandatory, + } + if self.id is not None: + data["id"] = self.id + if self.default_value is not None: + data["defaultValue"] = self.default_value + if self.order is not None: + data["order"] = self.order + if self.description is not None: + data["description"] = self.description + return data + + +@dataclass +class ConnectorRule: + """A rule definition for a Chronicle SOAR integration connector. + + Attributes: + display_name: Connector's rule data name. + type: Connector's rule data type. + """ + + display_name: str + type: ConnectorRuleType + + def to_dict(self) -> dict: + """Serialize to the dict shape expected by the Chronicle API.""" + return { + "displayName": self.display_name, + "type": str(self.type.value), + } + + class CasePriority(StrEnum): """Priority levels for cases.""" diff --git a/src/secops/chronicle/soar/__init__.py b/src/secops/chronicle/soar/__init__.py new file mode 100644 index 00000000..0e972623 --- /dev/null +++ b/src/secops/chronicle/soar/__init__.py @@ -0,0 +1,74 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Chronicle SOAR API specific functionality.""" + +from secops.chronicle.soar.client import SOARService + +from secops.chronicle.soar.integration.integrations import ( + list_integrations, + get_integration, + delete_integration, + create_integration, + transition_integration, + update_integration, + update_custom_integration, + get_integration_affected_items, + get_integration_dependencies, + get_integration_diff, + get_integration_restricted_agents, +) +from secops.chronicle.soar.integration.integration_instances import ( + list_integration_instances, + get_integration_instance, + delete_integration_instance, + create_integration_instance, + update_integration_instance, +) +from secops.chronicle.soar.integration.marketplace_integrations import ( + list_marketplace_integrations, + get_marketplace_integration, + get_marketplace_integration_diff, + install_marketplace_integration, + uninstall_marketplace_integration, +) + +__all__ = [ + # client + "SOARService", + # Integrations + "list_integrations", + "get_integration", + "delete_integration", + "create_integration", + "transition_integration", + "update_integration", + "update_custom_integration", + "get_integration_affected_items", + "get_integration_dependencies", + "get_integration_diff", + "get_integration_restricted_agents", + # Marketplace Integrations + "list_marketplace_integrations", + "get_marketplace_integration", + "get_marketplace_integration_diff", + "install_marketplace_integration", + "uninstall_marketplace_integration", + # Integration Instances + "list_integration_instances", + "get_integration_instance", + "delete_integration_instance", + "create_integration_instance", + "update_integration_instance", +] diff --git a/src/secops/chronicle/soar/client.py b/src/secops/chronicle/soar/client.py new file mode 100644 index 00000000..0f3d27a8 --- /dev/null +++ b/src/secops/chronicle/soar/client.py @@ -0,0 +1,1093 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Chronicle SOAR API client.""" + +from typing import TYPE_CHECKING, Any + +# pylint: disable=line-too-long +from secops.chronicle.models import ( + APIVersion, + DiffType, + IntegrationInstanceParameter, + IntegrationType, + PythonVersion, + TargetMode, + IntegrationParam, +) +from secops.chronicle.soar.integration.marketplace_integrations import ( + get_marketplace_integration as _get_marketplace_integration, + get_marketplace_integration_diff as _get_marketplace_integration_diff, + install_marketplace_integration as _install_marketplace_integration, + list_marketplace_integrations as _list_marketplace_integrations, + uninstall_marketplace_integration as _uninstall_marketplace_integration, +) +from secops.chronicle.soar.integration.integrations import ( + create_integration as _create_integration, + delete_integration as _delete_integration, + download_integration as _download_integration, + download_integration_dependency as _download_integration_dependency, + export_integration_items as _export_integration_items, + get_agent_integrations as _get_agent_integrations, + get_integration as _get_integration, + get_integration_affected_items as _get_integration_affected_items, + get_integration_dependencies as _get_integration_dependencies, + get_integration_diff as _get_integration_diff, + get_integration_restricted_agents as _get_integration_restricted_agents, + list_integrations as _list_integrations, + transition_integration as _transition_integration, + update_custom_integration as _update_custom_integration, + update_integration as _update_integration, +) +from secops.chronicle.soar.integration.integration_instances import ( + create_integration_instance as _create_integration_instance, + delete_integration_instance as _delete_integration_instance, + execute_integration_instance_test as _execute_integration_instance_test, + get_default_integration_instance as _get_default_integration_instance, + get_integration_instance as _get_integration_instance, + get_integration_instance_affected_items as _get_integration_instance_affected_items, + list_integration_instances as _list_integration_instances, + update_integration_instance as _update_integration_instance, +) + +# pylint: enable=line-too-long + +if TYPE_CHECKING: + from secops.chronicle.client import ChronicleClient + + +class SOARService: + """Namespace for all SOAR-related operations in Google SecOps.""" + + def __init__(self, client: "ChronicleClient"): + self._client = client + + # ------------------------------------------------------------------------- + # Marketplace Integration methods + # ------------------------------------------------------------------------- + + def list_marketplace_integrations( + self, + page_size: int | None = None, + page_token: str | None = None, + filter_string: str | None = None, + order_by: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + as_list: bool = False, + ) -> dict[str, Any] | list[dict[str, Any]]: + """Get a list of all marketplace integration. + + Args: + page_size: Maximum number of integration to return per page + page_token: Token for the next page of results, if available + filter_string: Filter expression to filter marketplace integration + order_by: Field to sort the marketplace integration by + api_version: API version to use. Defaults to V1BETA + as_list: If True, return a list of integration instead of a dict + with integration list and nextPageToken. + + Returns: + If as_list is True: List of marketplace integration. + If as_list is False: Dict with marketplace integration list and + nextPageToken. + + Raises: + APIError: If the API request fails + """ + return _list_marketplace_integrations( + self._client, + page_size, + page_token, + filter_string, + order_by, + api_version, + as_list, + ) + + def get_marketplace_integration( + self, + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Get a specific marketplace integration by integration name. + + Args: + integration_name: name of the marketplace integration to retrieve + api_version: API version to use. Defaults to V1BETA + + Returns: + Marketplace integration details + + Raises: + APIError: If the API request fails + """ + return _get_marketplace_integration( + self._client, integration_name, api_version + ) + + def get_marketplace_integration_diff( + self, + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Get the differences between the currently installed version of + an integration and the commercial version available in the + marketplace. + + Args: + integration_name: name of the marketplace integration + api_version: API version to use. Defaults to V1BETA + + Returns: + Marketplace integration diff details + + Raises: + APIError: If the API request fails + """ + return _get_marketplace_integration_diff( + self._client, integration_name, api_version + ) + + def install_marketplace_integration( + self, + integration_name: str, + override_mapping: bool | None = None, + staging: bool | None = None, + version: str | None = None, + restore_from_snapshot: bool | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Install a marketplace integration by integration name + + Args: + integration_name: Name of the marketplace integration to install + override_mapping: Optional. Determines if the integration should + override the ontology if already installed, if not provided, + set to false by default. + staging: Optional. Determines if the integration should be installed + as staging or production, + if not provided, installed as production. + version: Optional. Determines which version of the integration + should be installed. + restore_from_snapshot: Optional. Determines if the integration + should be installed from existing integration snapshot. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Installed marketplace integration details + + Raises: + APIError: If the API request fails + """ + return _install_marketplace_integration( + self._client, + integration_name, + override_mapping, + staging, + version, + restore_from_snapshot, + api_version, + ) + + def uninstall_marketplace_integration( + self, + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Uninstall a marketplace integration by integration name + + Args: + integration_name: Name of the marketplace integration to uninstall + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Empty dictionary if uninstallation is successful + + Raises: + APIError: If the API request fails + """ + return _uninstall_marketplace_integration( + self._client, integration_name, api_version + ) + + # ------------------------------------------------------------------------- + # Integration methods + # ------------------------------------------------------------------------- + + def list_integrations( + self, + page_size: int | None = None, + page_token: str | None = None, + filter_string: str | None = None, + order_by: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + as_list: bool = False, + ) -> dict[str, Any] | list[dict[str, Any]]: + """Get a list of all integrations. + + Args: + page_size: Maximum number of integrations to return per page + page_token: Token for the next page of results, if available + filter_string: Filter expression to filter integrations. + Only supports "displayName:" prefix. + order_by: Field to sort the integrations by + api_version: API version to use. Defaults to V1BETA + as_list: If True, return a list of integrations instead of a dict + with integration list and nextPageToken. + + Returns: + If as_list is True: List of integrations. + If as_list is False: Dict with integration list and nextPageToken. + + Raises: + APIError: If the API request fails + """ + return _list_integrations( + self._client, + page_size, + page_token, + filter_string, + order_by, + api_version, + as_list, + ) + + def get_integration( + self, + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Get a specific integration by integration name. + + Args: + integration_name: name of the integration to retrieve + api_version: API version to use. Defaults to V1BETA + + Returns: + Integration details + + Raises: + APIError: If the API request fails + """ + return _get_integration(self._client, integration_name, api_version) + + def delete_integration( + self, + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> None: + """Deletes a specific custom integration. Commercial integrations + cannot be deleted via this method. + + Args: + integration_name: Name of the integration to delete + api_version: API version to use for the request. + Default is V1BETA. + + Raises: + APIError: If the API request fails + """ + _delete_integration(self._client, integration_name, api_version) + + def create_integration( + self, + display_name: str, + staging: bool, + description: str | None = None, + image_base64: str | None = None, + svg_icon: str | None = None, + python_version: PythonVersion | None = None, + parameters: list[IntegrationParam | dict[str, Any]] | None = None, + categories: list[str] | None = None, + integration_type: IntegrationType | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Creates a new custom SOAR integration. + + Args: + display_name: Required. The display name of the integration + (max 150 characters) + staging: Required. True if the integration is in staging mode + description: Optional. The integration's description + (max 1,500 characters) + image_base64: Optional. The integration's image encoded as a + base64 string (max 5 MB) + svg_icon: Optional. The integration's SVG icon (max 1 MB) + python_version: Optional. The integration's Python version + parameters: Optional. Integration parameters (max 50). + Each entry may be an IntegrationParam dataclass instance + or a plain dict with keys: id, defaultValue, + displayName, propertyName, type, description, mandatory. + categories: Optional. Integration categories (max 50) + integration_type: Optional. The integration's type + (response/extension) + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Dict containing the details of the newly created integration + + Raises: + APIError: If the API request fails + """ + return _create_integration( + self._client, + display_name=display_name, + staging=staging, + description=description, + image_base64=image_base64, + svg_icon=svg_icon, + python_version=python_version, + parameters=parameters, + categories=categories, + integration_type=integration_type, + api_version=api_version, + ) + + def download_integration( + self, + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> bytes: + """Exports the entire integration package as a ZIP file. Includes + all scripts, definitions, and the manifest file. Use this method + for backup or sharing. + + Args: + integration_name: Name of the integration to download + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Bytes of the ZIP file containing the integration package + + Raises: + APIError: If the API request fails + """ + return _download_integration( + self._client, integration_name, api_version + ) + + def download_integration_dependency( + self, + integration_name: str, + dependency_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Initiates the download of a Python dependency (e.g., a library + from PyPI) for a custom integration. + + Args: + integration_name: Name of the integration whose dependency + to download + dependency_name: The dependency name to download. It can + contain the version or the repository. + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Empty dict if the download was successful, + or a dict containing error + details if the download failed + + Raises: + APIError: If the API request fails + """ + return _download_integration_dependency( + self._client, integration_name, dependency_name, api_version + ) + + def export_integration_items( + self, + integration_name: str, + actions: list[str] | str | None = None, + jobs: list[str] | str | None = None, + connectors: list[str] | str | None = None, + managers: list[str] | str | None = None, + transformers: list[str] | str | None = None, + logical_operators: list[str] | str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> bytes: + """Exports specific items from an integration into a ZIP folder. + Use this method to extract only a subset of capabilities (e.g., + just the connectors) for reuse. + + Args: + integration_name: Name of the integration to export items from + actions: Optional. IDs of the actions to export as a list or + comma-separated string. Format: [1,2,3] or "1,2,3" + jobs: Optional. IDs of the jobs to export as a list or + comma-separated string. + connectors: Optional. IDs of the connectors to export as a + list or comma-separated string. + managers: Optional. IDs of the managers to export as a list + or comma-separated string. + transformers: Optional. IDs of the transformers to export as + a list or comma-separated string. + logical_operators: Optional. IDs of the logical operators to + export as a list or comma-separated string. + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Bytes of the ZIP file containing the exported items + + Raises: + APIError: If the API request fails + """ + return _export_integration_items( + self._client, + integration_name, + actions=actions, + jobs=jobs, + connectors=connectors, + managers=managers, + transformers=transformers, + logical_operators=logical_operators, + api_version=api_version, + ) + + def get_integration_affected_items( + self, + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Identifies all system items (e.g., connector instances, job + instances, playbooks) that would be affected by a change to or + deletion of this integration. Use this method to conduct impact + analysis before making breaking changes. + + Args: + integration_name: Name of the integration to check for + affected items + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Dict containing the list of items affected by changes to + the specified integration + + Raises: + APIError: If the API request fails + """ + return _get_integration_affected_items( + self._client, integration_name, api_version + ) + + def get_agent_integrations( + self, + agent_id: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Returns the set of integrations currently installed and + configured on a specific agent. + + Args: + agent_id: The agent identifier + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Dict containing the list of agent-based integrations + + Raises: + APIError: If the API request fails + """ + return _get_agent_integrations(self._client, agent_id, api_version) + + def get_integration_dependencies( + self, + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Returns the complete list of Python dependencies currently + associated with a custom integration. + + Args: + integration_name: Name of the integration to check for + dependencies + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Dict containing the list of dependencies for the specified + integration + + Raises: + APIError: If the API request fails + """ + return _get_integration_dependencies( + self._client, integration_name, api_version + ) + + def get_integration_diff( + self, + integration_name: str, + diff_type: DiffType | None = DiffType.COMMERCIAL, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Get the configuration diff of a specific integration. + + Args: + integration_name: ID of the integration to retrieve the diff for + diff_type: Type of diff to retrieve (Commercial, Production, or + Staging). Default is Commercial. + COMMERCIAL: Diff between the commercial version of the + integration and the current version in the environment. + PRODUCTION: Returns the difference between the staging + integration and its matching production version. + STAGING: Returns the difference between the production + integration and its corresponding staging version. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the configuration diff of the specified integration + + Raises: + APIError: If the API request fails + """ + return _get_integration_diff( + self._client, integration_name, diff_type, api_version + ) + + def get_integration_restricted_agents( + self, + integration_name: str, + required_python_version: PythonVersion, + push_request: bool = False, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Identifies remote agents that would be restricted from running + an updated version of the integration, typically due to environment + incompatibilities like unsupported Python versions. + + Args: + integration_name: Name of the integration to check for + restricted agents + required_python_version: Python version required for the + updated integration + push_request: Optional. Indicates whether the integration is + being pushed to a different mode (production/staging). + False by default. + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Dict containing the list of agents that would be restricted + from running the updated integration + + Raises: + APIError: If the API request fails + """ + return _get_integration_restricted_agents( + self._client, + integration_name, + required_python_version=required_python_version, + push_request=push_request, + api_version=api_version, + ) + + def transition_integration( + self, + integration_name: str, + target_mode: TargetMode, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Transitions an integration to a different environment + (e.g. staging to production). + + Args: + integration_name: Name of the integration to transition + target_mode: Target mode to transition the integration to. + PRODUCTION: Transition the integration to production. + STAGING: Transition the integration to staging. + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Dict containing the details of the transitioned integration + + Raises: + APIError: If the API request fails + """ + return _transition_integration( + self._client, integration_name, target_mode, api_version + ) + + def update_integration( + self, + integration_name: str, + display_name: str | None = None, + description: str | None = None, + image_base64: str | None = None, + svg_icon: str | None = None, + python_version: PythonVersion | None = None, + parameters: list[dict[str, Any]] | None = None, + categories: list[str] | None = None, + integration_type: IntegrationType | None = None, + staging: bool | None = None, + dependencies_to_remove: list[str] | None = None, + update_mask: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Updates an existing integration's metadata. Use this method to + change the description or display image of a custom integration. + + Args: + integration_name: Name of the integration to update + display_name: Optional. The display name of the integration + (max 150 characters) + description: Optional. The integration's description + (max 1,500 characters) + image_base64: Optional. The integration's image encoded as a + base64 string (max 5 MB) + svg_icon: Optional. The integration's SVG icon (max 1 MB) + python_version: Optional. The integration's Python version + parameters: Optional. Integration parameters (max 50) + categories: Optional. Integration categories (max 50) + integration_type: Optional. The integration's type + (response/extension) + staging: Optional. True if the integration is in staging mode + dependencies_to_remove: Optional. List of dependencies to + remove from the integration + update_mask: Optional. Comma-separated list of fields to + update. If not provided, all non-None fields are updated. + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Dict containing the details of the updated integration + + Raises: + APIError: If the API request fails + """ + return _update_integration( + self._client, + integration_name, + display_name=display_name, + description=description, + image_base64=image_base64, + svg_icon=svg_icon, + python_version=python_version, + parameters=parameters, + categories=categories, + integration_type=integration_type, + staging=staging, + dependencies_to_remove=dependencies_to_remove, + update_mask=update_mask, + api_version=api_version, + ) + + def update_custom_integration( + self, + integration_name: str, + display_name: str | None = None, + description: str | None = None, + image_base64: str | None = None, + svg_icon: str | None = None, + python_version: PythonVersion | None = None, + parameters: list[dict[str, Any]] | None = None, + categories: list[str] | None = None, + integration_type: IntegrationType | None = None, + staging: bool | None = None, + dependencies_to_remove: list[str] | None = None, + update_mask: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Updates a custom integration definition, including its + parameters and dependencies. Use this method to refine the + operational behavior of a locally developed integration. + + Args: + integration_name: Name of the integration to update + display_name: Optional. The display name of the integration + (max 150 characters) + description: Optional. The integration's description + (max 1,500 characters) + image_base64: Optional. The integration's image encoded as a + base64 string (max 5 MB) + svg_icon: Optional. The integration's SVG icon (max 1 MB) + python_version: Optional. The integration's Python version + parameters: Optional. Integration parameters (max 50) + categories: Optional. Integration categories (max 50) + integration_type: Optional. The integration's type + (response/extension) + staging: Optional. True if the integration is in staging mode + dependencies_to_remove: Optional. List of dependencies to + remove from the integration + update_mask: Optional. Comma-separated list of fields to + update. If not provided, all non-None fields are updated. + api_version: API version to use for the request. + Default is V1BETA. + + Returns: + Dict containing: + - successful: Whether the integration was updated + successfully + - integration: The updated integration (if successful) + - dependencies: Dependency installation statuses + (if failed) + + Raises: + APIError: If the API request fails + """ + return _update_custom_integration( + self._client, + integration_name, + display_name=display_name, + description=description, + image_base64=image_base64, + svg_icon=svg_icon, + python_version=python_version, + parameters=parameters, + categories=categories, + integration_type=integration_type, + staging=staging, + dependencies_to_remove=dependencies_to_remove, + update_mask=update_mask, + api_version=api_version, + ) + + # ------------------------------------------------------------------------- + # Integration Instances methods + # ------------------------------------------------------------------------- + + def list_integration_instances( + self, + integration_name: str, + page_size: int | None = None, + page_token: str | None = None, + filter_string: str | None = None, + order_by: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + as_list: bool = False, + ) -> dict[str, Any] | list[dict[str, Any]]: + """List all instances for a specific integration. + + Use this method to browse the configured integration instances + available for a custom or third-party product across different + environments. + + Args: + integration_name: Name of the integration to list instances + for. + page_size: Maximum number of integration instances to + return. + page_token: Page token from a previous call to retrieve the + next page. + filter_string: Filter expression to filter integration + instances. + order_by: Field to sort the integration instances by. + api_version: API version to use for the request. Default is + V1BETA. + as_list: If True, return a list of integration instances + instead of a dict with integration instances list and + nextPageToken. + + Returns: + If as_list is True: List of integration instances. + If as_list is False: Dict with integration instances list + and nextPageToken. + + Raises: + APIError: If the API request fails. + """ + return _list_integration_instances( + self._client, + integration_name, + page_size=page_size, + page_token=page_token, + filter_string=filter_string, + order_by=order_by, + api_version=api_version, + as_list=as_list, + ) + + def get_integration_instance( + self, + integration_name: str, + integration_instance_id: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Get a single instance for a specific integration. + + Use this method to retrieve the specific configuration, + connection status, and environment mapping for an active + integration. + + Args: + integration_name: Name of the integration the instance + belongs to. + integration_instance_id: ID of the integration instance to + retrieve. + api_version: API version to use for the request. Default is + V1BETA. + + Returns: + Dict containing details of the specified + IntegrationInstance. + + Raises: + APIError: If the API request fails. + """ + return _get_integration_instance( + self._client, + integration_name, + integration_instance_id, + api_version=api_version, + ) + + def delete_integration_instance( + self, + integration_name: str, + integration_instance_id: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> None: + """Delete a specific integration instance. + + Use this method to permanently remove an integration instance + and stop all associated automated tasks (connectors or jobs) + using this instance. + + Args: + integration_name: Name of the integration the instance + belongs to. + integration_instance_id: ID of the integration instance to + delete. + api_version: API version to use for the request. Default is + V1BETA. + + Returns: + None + + Raises: + APIError: If the API request fails. + """ + return _delete_integration_instance( + self._client, + integration_name, + integration_instance_id, + api_version=api_version, + ) + + def create_integration_instance( + self, + integration_name: str, + environment: str, + display_name: str | None = None, + description: str | None = None, + parameters: ( + list[dict[str, Any] | IntegrationInstanceParameter] | None + ) = None, + agent: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Create a new integration instance for a specific + integration. + + Use this method to establish a new integration instance to a + custom or third-party security product for a specific + environment. All mandatory parameters required by the + integration definition must be provided. + + Args: + integration_name: Name of the integration to create the + instance for. + environment: The integration instance environment. Required. + display_name: The display name of the integration instance. + Automatically generated if not provided. Maximum 110 + characters. + description: The integration instance description. Maximum + 1500 characters. + parameters: List of IntegrationInstanceParameter instances + or dicts. + agent: Agent identifier for a remote integration instance. + api_version: API version to use for the request. Default is + V1BETA. + + Returns: + Dict containing the newly created IntegrationInstance + resource. + + Raises: + APIError: If the API request fails. + """ + return _create_integration_instance( + self._client, + integration_name, + environment, + display_name=display_name, + description=description, + parameters=parameters, + agent=agent, + api_version=api_version, + ) + + def update_integration_instance( + self, + integration_name: str, + integration_instance_id: str, + environment: str | None = None, + display_name: str | None = None, + description: str | None = None, + parameters: ( + list[dict[str, Any] | IntegrationInstanceParameter] | None + ) = None, + agent: str | None = None, + update_mask: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Update an existing integration instance. + + Use this method to modify connection parameters (e.g., rotate + an API key), change the display name, or update the description + of a configured integration instance. + + Args: + integration_name: Name of the integration the instance + belongs to. + integration_instance_id: ID of the integration instance to + update. + environment: The integration instance environment. + display_name: The display name of the integration instance. + Maximum 110 characters. + description: The integration instance description. Maximum + 1500 characters. + parameters: List of IntegrationInstanceParameter instances + or dicts. + agent: Agent identifier for a remote integration instance. + update_mask: Comma-separated list of fields to update. If + omitted, the mask is auto-generated from whichever + fields are provided. Example: + "displayName,description". + api_version: API version to use for the request. Default is + V1BETA. + + Returns: + Dict containing the updated IntegrationInstance resource. + + Raises: + APIError: If the API request fails. + """ + return _update_integration_instance( + self._client, + integration_name, + integration_instance_id, + environment=environment, + display_name=display_name, + description=description, + parameters=parameters, + agent=agent, + update_mask=update_mask, + api_version=api_version, + ) + + def execute_integration_instance_test( + self, + integration_name: str, + integration_instance_id: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Execute a connectivity test for a specific integration + instance. + + Use this method to verify that SecOps can successfully + communicate with the third-party security product using the + provided credentials. + + Args: + integration_name: Name of the integration the instance + belongs to. + integration_instance_id: ID of the integration instance to + test. + api_version: API version to use for the request. Default is + V1BETA. + + Returns: + Dict containing the test results with the following fields: + - successful: Indicates if the test was successful. + - message: Test result message (optional). + + Raises: + APIError: If the API request fails. + """ + return _execute_integration_instance_test( + self._client, + integration_name, + integration_instance_id, + api_version=api_version, + ) + + def get_integration_instance_affected_items( + self, + integration_name: str, + integration_instance_id: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """List all playbooks that depend on a specific integration + instance. + + Use this method to perform impact analysis before deleting or + significantly changing a connection configuration. + + Args: + integration_name: Name of the integration the instance + belongs to. + integration_instance_id: ID of the integration instance to + fetch affected items for. + api_version: API version to use for the request. Default is + V1BETA. + + Returns: + Dict containing a list of AffectedPlaybookResponse objects + that depend on the specified integration instance. + + Raises: + APIError: If the API request fails. + """ + return _get_integration_instance_affected_items( + self._client, + integration_name, + integration_instance_id, + api_version=api_version, + ) + + def get_default_integration_instance( + self, + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, + ) -> dict[str, Any]: + """Get the system default configuration for a specific + integration. + + Use this method to retrieve the baseline integration instance + details provided for a commercial product. + + Args: + integration_name: Name of the integration to fetch the + default instance for. + api_version: API version to use for the request. Default is + V1BETA. + + Returns: + Dict containing the default IntegrationInstance resource. + + Raises: + APIError: If the API request fails. + """ + return _get_default_integration_instance( + self._client, + integration_name, + api_version=api_version, + ) diff --git a/src/secops/chronicle/soar/integration/__init__.py b/src/secops/chronicle/soar/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/secops/chronicle/soar/integration/integration_instances.py b/src/secops/chronicle/soar/integration/integration_instances.py new file mode 100644 index 00000000..c7e88dd7 --- /dev/null +++ b/src/secops/chronicle/soar/integration/integration_instances.py @@ -0,0 +1,403 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Integration instances functionality for Chronicle.""" + +from typing import Any, TYPE_CHECKING + +from secops.chronicle.models import APIVersion, IntegrationInstanceParameter +from secops.chronicle.utils.format_utils import ( + format_resource_id, + build_patch_body, +) +from secops.chronicle.utils.request_utils import ( + chronicle_paginated_request, + chronicle_request, +) + +if TYPE_CHECKING: + from secops.chronicle.client import ChronicleClient + + +def list_integration_instances( + client: "ChronicleClient", + integration_name: str, + page_size: int | None = None, + page_token: str | None = None, + filter_string: str | None = None, + order_by: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + as_list: bool = False, +) -> dict[str, Any] | list[dict[str, Any]]: + """List all instances for a specific integration. + + Use this method to browse the configured integration instances available + for a custom or third-party product across different environments. + + Args: + client: ChronicleClient instance. + integration_name: Name of the integration to list instances for. + page_size: Maximum number of integration instances to return. + page_token: Page token from a previous call to retrieve the next page. + filter_string: Filter expression to filter integration instances. + order_by: Field to sort the integration instances by. + api_version: API version to use for the request. Default is V1BETA. + as_list: If True, return a list of integration instances instead of a + dict with integration instances list and nextPageToken. + + Returns: + If as_list is True: List of integration instances. + If as_list is False: Dict with integration instances list and + nextPageToken. + + Raises: + APIError: If the API request fails. + """ + extra_params = { + "filter": filter_string, + "orderBy": order_by, + } + + # Remove keys with None values + extra_params = {k: v for k, v in extra_params.items() if v is not None} + + return chronicle_paginated_request( + client, + api_version=api_version, + path=( + f"integrations/{format_resource_id(integration_name)}/" + f"integrationInstances" + ), + items_key="integrationInstances", + page_size=page_size, + page_token=page_token, + extra_params=extra_params, + as_list=as_list, + ) + + +def get_integration_instance( + client: "ChronicleClient", + integration_name: str, + integration_instance_id: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Get a single instance for a specific integration. + + Use this method to retrieve the specific configuration, connection status, + and environment mapping for an active integration. + + Args: + client: ChronicleClient instance. + integration_name: Name of the integration the instance belongs to. + integration_instance_id: ID of the integration instance to retrieve. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing details of the specified IntegrationInstance. + + Raises: + APIError: If the API request fails. + """ + return chronicle_request( + client, + method="GET", + endpoint_path=( + f"integrations/{format_resource_id(integration_name)}/" + f"integrationInstances/{integration_instance_id}" + ), + api_version=api_version, + ) + + +def delete_integration_instance( + client: "ChronicleClient", + integration_name: str, + integration_instance_id: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> None: + """Delete a specific integration instance. + + Use this method to permanently remove an integration instance and stop all + associated automated tasks (connectors or jobs) using this instance. + + Args: + client: ChronicleClient instance. + integration_name: Name of the integration the instance belongs to. + integration_instance_id: ID of the integration instance to delete. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + None + + Raises: + APIError: If the API request fails. + """ + chronicle_request( + client, + method="DELETE", + endpoint_path=( + f"integrations/{format_resource_id(integration_name)}/" + f"integrationInstances/{integration_instance_id}" + ), + api_version=api_version, + ) + + +def create_integration_instance( + client: "ChronicleClient", + integration_name: str, + environment: str, + display_name: str | None = None, + description: str | None = None, + parameters: ( + list[dict[str, Any] | IntegrationInstanceParameter] | None + ) = None, + agent: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Create a new integration instance for a specific integration. + + Use this method to establish a new integration instance to a custom or + third-party security product for a specific environment. All mandatory + parameters required by the integration definition must be provided. + + Args: + client: ChronicleClient instance. + integration_name: Name of the integration to create the instance for. + environment: The integration instance environment. Required. + display_name: The display name of the integration instance. + Automatically generated if not provided. Maximum 110 characters. + description: The integration instance description. Maximum 1500 + characters. + parameters: List of IntegrationInstanceParameter instances or dicts. + agent: Agent identifier for a remote integration instance. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the newly created IntegrationInstance resource. + + Raises: + APIError: If the API request fails. + """ + resolved_parameters = ( + [ + p.to_dict() if isinstance(p, IntegrationInstanceParameter) else p + for p in parameters + ] + if parameters is not None + else None + ) + + body = { + "environment": environment, + "displayName": display_name, + "description": description, + "parameters": resolved_parameters, + "agent": agent, + } + + # Remove keys with None values + body = {k: v for k, v in body.items() if v is not None} + + return chronicle_request( + client, + method="POST", + endpoint_path=( + f"integrations/{format_resource_id(integration_name)}/" + f"integrationInstances" + ), + api_version=api_version, + json=body, + ) + + +def update_integration_instance( + client: "ChronicleClient", + integration_name: str, + integration_instance_id: str, + environment: str | None = None, + display_name: str | None = None, + description: str | None = None, + parameters: ( + list[dict[str, Any] | IntegrationInstanceParameter] | None + ) = None, + agent: str | None = None, + update_mask: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Update an existing integration instance. + + Use this method to modify connection parameters (e.g., rotate an API + key), change the display name, or update the description of a configured + integration instance. + + Args: + client: ChronicleClient instance. + integration_name: Name of the integration the instance belongs to. + integration_instance_id: ID of the integration instance to update. + environment: The integration instance environment. + display_name: The display name of the integration instance. Maximum + 110 characters. + description: The integration instance description. Maximum 1500 + characters. + parameters: List of IntegrationInstanceParameter instances or dicts. + agent: Agent identifier for a remote integration instance. + update_mask: Comma-separated list of fields to update. If omitted, + the mask is auto-generated from whichever fields are provided. + Example: "displayName,description". + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the updated IntegrationInstance resource. + + Raises: + APIError: If the API request fails. + """ + resolved_parameters = ( + [ + p.to_dict() if isinstance(p, IntegrationInstanceParameter) else p + for p in parameters + ] + if parameters is not None + else None + ) + + body, params = build_patch_body( + field_map=[ + ("environment", "environment", environment), + ("displayName", "displayName", display_name), + ("description", "description", description), + ("parameters", "parameters", resolved_parameters), + ("agent", "agent", agent), + ], + update_mask=update_mask, + ) + + return chronicle_request( + client, + method="PATCH", + endpoint_path=( + f"integrations/{format_resource_id(integration_name)}/" + f"integrationInstances/{integration_instance_id}" + ), + api_version=api_version, + json=body, + params=params, + ) + + +def execute_integration_instance_test( + client: "ChronicleClient", + integration_name: str, + integration_instance_id: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Execute a connectivity test for a specific integration instance. + + Use this method to verify that SecOps can successfully communicate with + the third-party security product using the provided credentials. + + Args: + client: ChronicleClient instance. + integration_name: Name of the integration the instance belongs to. + integration_instance_id: ID of the integration instance to test. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the test results with the following fields: + - successful: Indicates if the test was successful. + - message: Test result message (optional). + + Raises: + APIError: If the API request fails. + """ + return chronicle_request( + client, + method="POST", + endpoint_path=( + f"integrations/{format_resource_id(integration_name)}/" + f"integrationInstances/{integration_instance_id}:executeTest" + ), + api_version=api_version, + ) + + +def get_integration_instance_affected_items( + client: "ChronicleClient", + integration_name: str, + integration_instance_id: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """List all playbooks that depend on a specific integration instance. + + Use this method to perform impact analysis before deleting or + significantly changing a connection configuration. + + Args: + client: ChronicleClient instance. + integration_name: Name of the integration the instance belongs to. + integration_instance_id: ID of the integration instance to fetch + affected items for. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing a list of AffectedPlaybookResponse objects that + depend on the specified integration instance. + + Raises: + APIError: If the API request fails. + """ + return chronicle_request( + client, + method="GET", + endpoint_path=( + f"integrations/{format_resource_id(integration_name)}/" + f"integrationInstances/{integration_instance_id}:fetchAffectedItems" + ), + api_version=api_version, + ) + + +def get_default_integration_instance( + client: "ChronicleClient", + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Get the system default configuration for a specific integration. + + Use this method to retrieve the baseline integration instance details + provided for a commercial product. + + Args: + client: ChronicleClient instance. + integration_name: Name of the integration to fetch the default + instance for. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the default IntegrationInstance resource. + + Raises: + APIError: If the API request fails. + """ + return chronicle_request( + client, + method="GET", + endpoint_path=( + f"integrations/{format_resource_id(integration_name)}/" + f"integrationInstances:fetchDefaultInstance" + ), + api_version=api_version, + ) diff --git a/src/secops/chronicle/soar/integration/integrations.py b/src/secops/chronicle/soar/integration/integrations.py new file mode 100644 index 00000000..4f72f5ea --- /dev/null +++ b/src/secops/chronicle/soar/integration/integrations.py @@ -0,0 +1,686 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Integrations functionality for Chronicle.""" + +from typing import Any, TYPE_CHECKING + +from secops.chronicle.models import ( + APIVersion, + DiffType, + IntegrationParam, + TargetMode, + PythonVersion, + IntegrationType, +) + +from secops.chronicle.utils.format_utils import build_patch_body +from secops.chronicle.utils.request_utils import ( + chronicle_paginated_request, + chronicle_request_bytes, + chronicle_request, +) + +if TYPE_CHECKING: + from secops.chronicle.client import ChronicleClient + + +def list_integrations( + client: "ChronicleClient", + page_size: int | None = None, + page_token: str | None = None, + filter_string: str | None = None, + order_by: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + as_list: bool = False, +) -> dict[str, Any] | list[dict[str, Any]]: + """Get a list of integrations. + + Args: + client: ChronicleClient instance + page_size: Number of results to return per page + page_token: Token for the page to retrieve + filter_string: Filter expression to filter integrations + order_by: Field to sort the integrations by + api_version: API version to use for the request. Default is V1BETA. + as_list: If True, return a list of integrations instead + of a dict with integrations list and nextPageToken. + + Returns: + If as_list is True: List of integrations. + If as_list is False: Dict with integrations list and + nextPageToken. + + Raises: + APIError: If the API request fails + """ + param_fields = { + "filter": filter_string, + "orderBy": order_by, + } + + # Remove keys with None values + param_fields = {k: v for k, v in param_fields.items() if v is not None} + + return chronicle_paginated_request( + client, + api_version=api_version, + path="integrations", + items_key="integrations", + page_size=page_size, + page_token=page_token, + extra_params=param_fields, + as_list=as_list, + ) + + +def get_integration( + client: "ChronicleClient", + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Get details of a specific integration. + + Args: + client: ChronicleClient instance + integration_name: name of the integration to retrieve + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing details of the specified integration + + Raises: + APIError: If the API request fails + """ + return chronicle_request( + client, + method="GET", + endpoint_path=f"integrations/{integration_name}", + api_version=api_version, + ) + + +def delete_integration( + client: "ChronicleClient", + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> None: + """Deletes a specific custom Integration. Commercial integrations cannot + be deleted via this method. + + Args: + client: ChronicleClient instance + integration_name: name of the integration to delete + api_version: API version to use for the request. Default is V1BETA. + + Raises: + APIError: If the API request fails + """ + chronicle_request( + client, + method="DELETE", + endpoint_path=f"integrations/{integration_name}", + api_version=api_version, + ) + + +def create_integration( + client: "ChronicleClient", + display_name: str, + staging: bool, + description: str | None = None, + image_base64: str | None = None, + svg_icon: str | None = None, + python_version: PythonVersion | None = None, + parameters: list[IntegrationParam | dict[str, Any]] | None = None, + categories: list[str] | None = None, + integration_type: IntegrationType | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Creates a new custom SOAR Integration. + + Args: + client: ChronicleClient instance + display_name: Required. The display name of the integration + (max 150 characters) + staging: Required. True if the integration is in staging mode + description: Optional. The integration's description + (max 1,500 characters) + image_base64: Optional. The integration's image encoded as + a base64 string (max 5 MB) + svg_icon: Optional. The integration's SVG icon (max 1 MB) + python_version: Optional. The integration's Python version + parameters: Optional. Integration parameters (max 50). Each entry may + be an IntegrationParam dataclass instance or a plain dict with + keys: id, defaultValue, displayName, propertyName, type, + description, mandatory. + categories: Optional. Integration categories (max 50) + integration_type: Optional. The integration's type (response/extension) + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the details of the newly created integration + + Raises: + APIError: If the API request fails + """ + serialised_params: list[dict[str, Any]] | None = None + if parameters is not None: + serialised_params = [ + p.to_dict() if isinstance(p, IntegrationParam) else p + for p in parameters + ] + + body_fields = { + "displayName": display_name, + "staging": staging, + "description": description, + "imageBase64": image_base64, + "svgIcon": svg_icon, + "pythonVersion": python_version, + "parameters": serialised_params, + "categories": categories, + "type": integration_type, + } + + # Remove keys with None values + body_fields = {k: v for k, v in body_fields.items() if v is not None} + + return chronicle_request( + client, + method="POST", + endpoint_path="integrations", + json=body_fields, + api_version=api_version, + ) + + +def download_integration( + client: "ChronicleClient", + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> bytes: + """Exports the entire integration package as a ZIP file. Includes all + scripts, definitions, and the manifest file. Use this method for backup + or sharing. + + Args: + client: ChronicleClient instance + integration_name: name of the integration to download + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Bytes of the ZIP file containing the integration package + + Raises: + APIError: If the API request fails + """ + return chronicle_request_bytes( + client, + method="GET", + endpoint_path=f"integrations/{integration_name}:export", + api_version=api_version, + params={"alt": "media"}, + headers={"Accept": "application/zip"}, + ) + + +def download_integration_dependency( + client: "ChronicleClient", + integration_name: str, + dependency_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Initiates the download of a Python dependency (e.g., a library from + PyPI) for a custom integration. + + Args: + client: ChronicleClient instance + integration_name: name of the integration whose dependency to download + dependency_name: The dependency name to download. It can contain the + version or the repository. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Empty dict if the download was successful, or a dict containing error + details if the download failed + + Raises: + APIError: If the API request fails + """ + return chronicle_request( + client, + method="POST", + endpoint_path=f"integrations/{integration_name}:downloadDependency", + json={"dependency": dependency_name}, + api_version=api_version, + ) + + +def export_integration_items( + client: "ChronicleClient", + integration_name: str, + actions: list[str] | None = None, + jobs: list[str] | None = None, + connectors: list[str] | None = None, + managers: list[str] | None = None, + transformers: list[str] | None = None, + logical_operators: list[str] | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> bytes: + """Exports specific items from an integration into a ZIP folder. Use + this method to extract only a subset of capabilities (e.g., just the + connectors) for reuse. + + Args: + client: ChronicleClient instance + integration_name: name of the integration to export items from + actions: Optional. A list the ids of the actions to export. Format: + [1,2,3] + jobs: Optional. A list the ids of the jobs to export. Format: + [1,2,3] + connectors: Optional. A list the ids of the connectors to export. + Format: [1,2,3] + managers: Optional. A list the ids of the managers to export. Format: + [1,2,3] + transformers: Optional. A list the ids of the transformers to export. + Format: [1,2,3] + logical_operators: Optional. A list the ids of the logical + operators to export. Format: [1,2,3] + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Bytes of the ZIP file containing the exported integration items + + Raises: + APIError: If the API request fails + """ + export_items = { + "actions": ",".join(actions) if actions else None, + "jobs": jobs, + "connectors": connectors, + "managers": managers, + "transformers": transformers, + "logicalOperators": logical_operators, + "alt": "media", + } + + # Remove keys with None values + export_items = {k: v for k, v in export_items.items() if v is not None} + + return chronicle_request_bytes( + client, + method="GET", + endpoint_path=f"integrations/{integration_name}:exportItems", + params=export_items, + api_version=api_version, + headers={"Accept": "application/zip"}, + ) + + +def get_integration_affected_items( + client: "ChronicleClient", + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Identifies all system items (e.g., connector instances, job instances, + playbooks) that would be affected by a change to or deletion of this + integration. Use this method to conduct impact analysis before making + breaking changes. + + Args: + client: ChronicleClient instance + integration_name: name of the integration to check for affected items + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the list of items affected by changes to the specified + integration + + Raises: + APIError: If the API request fails + """ + return chronicle_request( + client, + method="GET", + endpoint_path=f"integrations/{integration_name}:fetchAffectedItems", + api_version=api_version, + ) + + +def get_agent_integrations( + client: "ChronicleClient", + agent_id: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Returns the set of integrations currently installed and configured on + a specific agent. + + Args: + client: ChronicleClient instance + agent_id: The agent identifier + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the list of agent-based integrations + + Raises: + APIError: If the API request fails + """ + return chronicle_request( + client, + method="GET", + endpoint_path="integrations:fetchAgentIntegrations", + params={"agentId": agent_id}, + api_version=api_version, + ) + + +def get_integration_dependencies( + client: "ChronicleClient", + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Returns the complete list of Python dependencies currently associated + with a custom integration. + + Args: + client: ChronicleClient instance + integration_name: name of the integration to check for dependencies + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the list of dependencies for the specified integration + + Raises: + APIError: If the API request fails + """ + return chronicle_request( + client, + method="GET", + endpoint_path=f"integrations/{integration_name}:fetchDependencies", + api_version=api_version, + ) + + +def get_integration_restricted_agents( + client: "ChronicleClient", + integration_name: str, + required_python_version: PythonVersion, + push_request: bool = False, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Identifies remote agents that would be restricted from running an + updated version of the integration, typically due to environment + incompatibilities like unsupported Python versions. + + Args: + client: ChronicleClient instance + integration_name: name of the integration to check for restricted agents + required_python_version: Python version required for the updated + integration. + push_request: Optional. Indicates whether the integration is + pushed to a different mode (production/staging). False by default. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the list of agents that would be restricted from running + + Raises: + APIError: If the API request fails + """ + params_fields = { + "requiredPythonVersion": required_python_version.value, + "pushRequest": push_request, + } + + # Remove keys with None values + params_fields = {k: v for k, v in params_fields.items() if v is not None} + + return chronicle_request( + client, + method="GET", + endpoint_path=f"integrations/{integration_name}:fetchRestrictedAgents", + params=params_fields, + api_version=api_version, + ) + + +def get_integration_diff( + client: "ChronicleClient", + integration_name: str, + diff_type: DiffType = DiffType.COMMERCIAL, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Get the configuration diff of a specific integration. + + Args: + client: ChronicleClient instance + integration_name: ID of the integration to retrieve the diff for + diff_type: Type of diff to retrieve + (Commercial, Production, or Staging). Default is Commercial. + COMMERCIAL: Diff between the commercial version of the + integration and the current version in the environment. + PRODUCTION: Returns the difference between the staging + integration and its matching production version. + STAGING: Returns the difference between the production + integration and its corresponding staging version. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the configuration diff of the specified integration + + Raises: + APIError: If the API request fails + """ + return chronicle_request( + client, + method="GET", + endpoint_path=f"integrations/{integration_name}" + f":fetch{diff_type.value}Diff", + api_version=api_version, + ) + + +def transition_integration( + client: "ChronicleClient", + integration_name: str, + target_mode: TargetMode, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Transition an integration to a different environment + (e.g. staging to production). + + Args: + client: ChronicleClient instance + integration_name: ID of the integration to transition + target_mode: Target mode to transition the integration to: + PRODUCTION: Transition the integration to production environment. + STAGING: Transition the integration to staging environment. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the details of the transitioned integration + + Raises: + APIError: If the API request fails + """ + return chronicle_request( + client, + method="POST", + endpoint_path=f"integrations/{integration_name}" + f":pushTo{target_mode.value}", + api_version=api_version, + ) + + +def update_integration( + client: "ChronicleClient", + integration_name: str, + display_name: str | None = None, + description: str | None = None, + image_base64: str | None = None, + svg_icon: str | None = None, + python_version: PythonVersion | None = None, + parameters: list[dict[str, Any]] | None = None, + categories: list[str] | None = None, + integration_type: IntegrationType | None = None, + staging: bool | None = None, + dependencies_to_remove: list[str] | None = None, + update_mask: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Update an existing integration. + + Args: + client: ChronicleClient instance + integration_name: ID of the integration to update + display_name: Optional. The display name of the integration + (max 150 characters) + description: Optional. The integration's description + (max 1,500 characters) + image_base64: Optional. The integration's image encoded as a + base64 string (max 5 MB) + svg_icon: Optional. The integration's SVG icon (max 1 MB) + python_version: Optional. The integration's Python version + parameters: Optional. Integration parameters (max 50) + categories: Optional. Integration categories (max 50) + integration_type: Optional. The integration's type (response/extension) + staging: Optional. True if the integration is in staging mode + dependencies_to_remove: Optional. List of dependencies to + remove from the integration. + update_mask: Optional. Comma-separated list of fields to update. + If not provided, all non-None fields will be updated. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing the details of the updated integration + + Raises: + APIError: If the API request fails + """ + body, params = build_patch_body( + field_map=[ + ("displayName", "display_name", display_name), + ("description", "description", description), + ("imageBase64", "image_base64", image_base64), + ("svgIcon", "svg_icon", svg_icon), + ("pythonVersion", "python_version", python_version), + ("parameters", "parameters", parameters), + ("categories", "categories", categories), + ("integrationType", "integration_type", integration_type), + ("staging", "staging", staging), + ], + update_mask=update_mask, + ) + + if dependencies_to_remove is not None: + params = params or {} + params["dependenciesToRemove"] = ",".join(dependencies_to_remove) + + return chronicle_request( + client, + method="PATCH", + endpoint_path=f"integrations/{integration_name}", + json=body, + params=params, + api_version=api_version, + ) + + +def update_custom_integration( + client: "ChronicleClient", + integration_name: str, + display_name: str | None = None, + description: str | None = None, + image_base64: str | None = None, + svg_icon: str | None = None, + python_version: PythonVersion | None = None, + parameters: list[dict[str, Any]] | None = None, + categories: list[str] | None = None, + integration_type: IntegrationType | None = None, + staging: bool | None = None, + dependencies_to_remove: list[str] | None = None, + update_mask: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Updates a custom integration definition, including its parameters and + dependencies. Use this method to refine the operational behavior of a + locally developed integration. + + Args: + client: ChronicleClient instance + integration_name: Name of the integration to update + display_name: Optional. The display name of the integration + (max 150 characters) + description: Optional. The integration's description + (max 1,500 characters) + image_base64: Optional. The integration's image encoded as a + base64 string (max 5 MB) + svg_icon: Optional. The integration's SVG icon (max 1 MB) + python_version: Optional. The integration's Python version + parameters: Optional. Integration parameters (max 50) + categories: Optional. Integration categories (max 50) + integration_type: Optional. The integration's type (response/extension) + staging: Optional. True if the integration is in staging mode + dependencies_to_remove: Optional. List of dependencies to remove from + the integration + update_mask: Optional. Comma-separated list of fields to update. + If not provided, all non-None fields will be updated. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Dict containing: + - successful: Whether the integration was updated successfully + - integration: The updated integration (populated if successful) + - dependencies: Dependency installation statuses + (populated if failed) + + Raises: + APIError: If the API request fails + """ + integration_fields = { + "name": integration_name, + "displayName": display_name, + "description": description, + "imageBase64": image_base64, + "svgIcon": svg_icon, + "pythonVersion": python_version, + "parameters": parameters, + "categories": categories, + "type": integration_type, + "staging": staging, + } + + # Remove keys with None values + integration_fields = { + k: v for k, v in integration_fields.items() if v is not None + } + + body = {"integration": integration_fields} + + if dependencies_to_remove is not None: + body["dependenciesToRemove"] = dependencies_to_remove + + params = {"updateMask": update_mask} if update_mask else None + + return chronicle_request( + client, + method="POST", + endpoint_path=f"integrations/" + f"{integration_name}:updateCustomIntegration", + json=body, + params=params, + api_version=api_version, + ) diff --git a/src/secops/chronicle/soar/integration/marketplace_integrations.py b/src/secops/chronicle/soar/integration/marketplace_integrations.py new file mode 100644 index 00000000..fb9006cc --- /dev/null +++ b/src/secops/chronicle/soar/integration/marketplace_integrations.py @@ -0,0 +1,199 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Integrations functionality for Chronicle.""" + +from typing import Any, TYPE_CHECKING + +from secops.chronicle.models import APIVersion +from secops.chronicle.utils.request_utils import ( + chronicle_paginated_request, + chronicle_request, +) + +if TYPE_CHECKING: + from secops.chronicle.client import ChronicleClient + + +def list_marketplace_integrations( + client: "ChronicleClient", + page_size: int | None = None, + page_token: str | None = None, + filter_string: str | None = None, + order_by: str | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, + as_list: bool = False, +) -> dict[str, Any] | list[dict[str, Any]]: + """Get a list of marketplace integrations. + + Args: + client: ChronicleClient instance + page_size: Number of results to return per page + page_token: Token for the page to retrieve + filter_string: Filter expression to filter marketplace integrations + order_by: Field to sort the marketplace integrations by + api_version: API version to use for the request. Default is V1BETA. + as_list: If True, return a list of marketplace integrations instead + of a dict with marketplace integrations list and nextPageToken. + + Returns: + If as_list is True: List of marketplace integrations. + If as_list is False: Dict with marketplace integrations list and + nextPageToken. + + Raises: + APIError: If the API request fails + """ + field_map = { + "filter": filter_string, + "orderBy": order_by, + } + + return chronicle_paginated_request( + client, + api_version=api_version, + path="marketplaceIntegrations", + items_key="marketplaceIntegrations", + page_size=page_size, + page_token=page_token, + extra_params={k: v for k, v in field_map.items() if v is not None}, + as_list=as_list, + ) + + +def get_marketplace_integration( + client: "ChronicleClient", + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Get a marketplace integration by integration name + + Args: + client: ChronicleClient instance + integration_name: Name of the marketplace integration to retrieve + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Marketplace integration details + + Raises: + APIError: If the API request fails + """ + return chronicle_request( + client, + method="GET", + endpoint_path=f"marketplaceIntegrations/{integration_name}", + api_version=api_version, + ) + + +def get_marketplace_integration_diff( + client: "ChronicleClient", + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Get the differences between the currently installed version of + an integration and the commercial version available in the marketplace. + + Args: + client: ChronicleClient instance + integration_name: Name of the marketplace integration to compare + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Marketplace integration diff details + + Raises: + APIError: If the API request fails + """ + return chronicle_request( + client, + method="GET", + endpoint_path=f"marketplaceIntegrations/{integration_name}" + f":fetchCommercialDiff", + api_version=api_version, + ) + + +def install_marketplace_integration( + client: "ChronicleClient", + integration_name: str, + override_mapping: bool | None = None, + staging: bool | None = None, + version: str | None = None, + restore_from_snapshot: bool | None = None, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Install a marketplace integration by integration name + + Args: + client: ChronicleClient instance + integration_name: Name of the marketplace integration to install + override_mapping: Optional. Determines if the integration should + override the ontology if already installed, if not provided, set to + false by default. + staging: Optional. Determines if the integration should be installed + as staging or production, if not provided, installed as production. + version: Optional. Determines which version of the integration + should be installed. + restore_from_snapshot: Optional. Determines if the integration + should be installed from existing integration snapshot. + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Installed marketplace integration details + + Raises: + APIError: If the API request fails + """ + field_map = { + "overrideMapping": override_mapping, + "staging": staging, + "version": version, + "restoreFromSnapshot": restore_from_snapshot, + } + + return chronicle_request( + client, + method="POST", + endpoint_path=f"marketplaceIntegrations/{integration_name}:install", + json={k: v for k, v in field_map.items() if v is not None}, + api_version=api_version, + ) + + +def uninstall_marketplace_integration( + client: "ChronicleClient", + integration_name: str, + api_version: APIVersion | None = APIVersion.V1BETA, +) -> dict[str, Any]: + """Uninstall a marketplace integration by integration name + + Args: + client: ChronicleClient instance + integration_name: Name of the marketplace integration to uninstall + api_version: API version to use for the request. Default is V1BETA. + + Returns: + Empty dictionary if uninstallation is successful + + Raises: + APIError: If the API request fails + """ + return chronicle_request( + client, + method="POST", + endpoint_path=f"marketplaceIntegrations/{integration_name}:uninstall", + api_version=api_version, + ) diff --git a/src/secops/chronicle/stats.py b/src/secops/chronicle/stats.py index 99b46309..42e31aba 100644 --- a/src/secops/chronicle/stats.py +++ b/src/secops/chronicle/stats.py @@ -13,6 +13,7 @@ # limitations under the License. # """Statistics functionality for Chronicle searches.""" + from datetime import datetime from typing import Any, TYPE_CHECKING diff --git a/src/secops/chronicle/utils/format_utils.py b/src/secops/chronicle/utils/format_utils.py index b6567528..126ae503 100644 --- a/src/secops/chronicle/utils/format_utils.py +++ b/src/secops/chronicle/utils/format_utils.py @@ -65,3 +65,34 @@ def parse_json_list( except ValueError as e: raise APIError(f"Invalid {field_name} JSON") from e return value + + +# pylint: disable=line-too-long +def build_patch_body( + field_map: list[tuple[str, str, Any]], + update_mask: str | None = None, +) -> tuple[dict[str, Any], dict[str, Any] | None]: + """Build a request body and params dict for a PATCH request. + + Args: + field_map: List of (api_key, mask_key, value) tuples for + each optional field. + update_mask: Explicit update mask. If provided, + overrides the auto-generated mask. + + Returns: + Tuple of (body, params) where params contains the updateMask or is None. + """ + body = { + api_key: value for api_key, _, value in field_map if value is not None + } + mask_fields = [ + mask_key for _, mask_key, value in field_map if value is not None + ] + + resolved_mask = update_mask or ( + ",".join(mask_fields) if mask_fields else None + ) + params = {"updateMask": resolved_mask} if resolved_mask else None + + return body, params diff --git a/src/secops/chronicle/utils/request_utils.py b/src/secops/chronicle/utils/request_utils.py index 43f2d885..c3b2cd8a 100644 --- a/src/secops/chronicle/utils/request_utils.py +++ b/src/secops/chronicle/utils/request_utils.py @@ -14,7 +14,7 @@ # """Helper functions for Chronicle.""" -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Optional import requests from google.auth.exceptions import GoogleAuthError @@ -297,3 +297,66 @@ def chronicle_request( ) return data + + +def chronicle_request_bytes( + client: "ChronicleClient", + method: str, + endpoint_path: str, + *, + api_version: str = APIVersion.V1, + params: Optional[dict[str, Any]] = None, + headers: Optional[dict[str, Any]] = None, + expected_status: int | set[int] | tuple[int, ...] | list[int] = 200, + error_message: str | None = None, + timeout: int | None = None, +) -> bytes: + base = f"{client.base_url(api_version)}/{client.instance_id}" + + if endpoint_path.startswith(":"): + url = f"{base}{endpoint_path}" + else: + url = f'{base}/{endpoint_path.lstrip("/")}' + + try: + response = client.session.request( + method=method, + url=url, + params=params, + headers=headers, + timeout=timeout, + stream=True, + ) + except GoogleAuthError as exc: + base_msg = error_message or "Google authentication failed" + raise APIError(f"{base_msg}: authentication_error={exc}") from exc + except requests.RequestException as exc: + base_msg = error_message or "API request failed" + raise APIError( + f"{base_msg}: method={method}, url={url}, " + f"request_error={exc.__class__.__name__}, detail={exc}" + ) from exc + + if isinstance(expected_status, (set, tuple, list)): + status_ok = response.status_code in expected_status + else: + status_ok = response.status_code == expected_status + + if not status_ok: + # try json for detail, else preview text + try: + data = response.json() + raise APIError( + f"{error_message or 'API request failed'}: method={method}, url={url}, " + f"status={response.status_code}, response={data}" + ) from None + except ValueError: + preview = _safe_body_preview( + getattr(response, "text", ""), limit=MAX_BODY_CHARS + ) + raise APIError( + f"{error_message or 'API request failed'}: method={method}, url={url}, " + f"status={response.status_code}, response_text={preview}" + ) from None + + return response.content diff --git a/src/secops/cli/cli_client.py b/src/secops/cli/cli_client.py index 4c483656..65b787f2 100644 --- a/src/secops/cli/cli_client.py +++ b/src/secops/cli/cli_client.py @@ -39,6 +39,9 @@ from secops.cli.commands.udm_search import setup_udm_search_view_command from secops.cli.commands.watchlist import setup_watchlist_command from secops.cli.commands.rule_retrohunt import setup_rule_retrohunt_command +from secops.cli.commands.integration.integration_client import ( + setup_integrations_command, +) from secops.cli.utils.common_args import add_chronicle_args, add_common_args from secops.cli.utils.config_utils import load_config from secops.exceptions import AuthenticationError, SecOpsError @@ -189,6 +192,7 @@ def build_parser() -> argparse.ArgumentParser: setup_dashboard_query_command(subparsers) setup_watchlist_command(subparsers) setup_rule_retrohunt_command(subparsers) + setup_integrations_command(subparsers) return parser diff --git a/src/secops/cli/commands/__init__.py b/src/secops/cli/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/secops/cli/commands/integration/__init__.py b/src/secops/cli/commands/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/secops/cli/commands/integration/integration.py b/src/secops/cli/commands/integration/integration.py new file mode 100644 index 00000000..55c94f05 --- /dev/null +++ b/src/secops/cli/commands/integration/integration.py @@ -0,0 +1,775 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Google SecOps CLI integration commands""" + +import sys + +from secops.chronicle.models import ( + DiffType, + IntegrationType, + PythonVersion, + TargetMode, +) +from secops.cli.utils.formatters import output_formatter +from secops.cli.utils.common_args import ( + add_pagination_args, + add_as_list_arg, +) + + +def setup_integrations_command(subparsers): + """Setup integrations command""" + integrations_parser = subparsers.add_parser( + "integrations", help="Manage SecOps integrations" + ) + lvl1 = integrations_parser.add_subparsers( + dest="integrations_command", help="Integrations command" + ) + + # list command + list_parser = lvl1.add_parser("list", help="List integrations") + add_pagination_args(list_parser) + add_as_list_arg(list_parser) + list_parser.add_argument( + "--filter-string", + type=str, + help="Filter string for listing integrations", + dest="filter_string", + ) + list_parser.add_argument( + "--order-by", + type=str, + help="Order by string for listing integrations", + dest="order_by", + ) + list_parser.set_defaults(func=handle_integration_list_command) + + # get command + get_parser = lvl1.add_parser("get", help="Get integration details") + get_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration to get details for", + dest="integration_id", + required=True, + ) + get_parser.set_defaults(func=handle_integration_get_command) + + # delete command + delete_parser = lvl1.add_parser("delete", help="Delete an integration") + delete_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration to delete", + dest="integration_id", + required=True, + ) + delete_parser.set_defaults(func=handle_integration_delete_command) + + # create command + create_parser = lvl1.add_parser( + "create", help="Create a new custom integration" + ) + create_parser.add_argument( + "--display-name", + type=str, + help="Display name for the integration (max 150 characters)", + dest="display_name", + required=True, + ) + create_parser.add_argument( + "--staging", + action="store_true", + help="Create the integration in staging mode", + dest="staging", + ) + create_parser.add_argument( + "--description", + type=str, + help="Description of the integration (max 1,500 characters)", + dest="description", + ) + create_parser.add_argument( + "--image-base64", + type=str, + help="Integration image encoded as a base64 string (max 5 MB)", + dest="image_base64", + ) + create_parser.add_argument( + "--svg-icon", + type=str, + help="Integration SVG icon string (max 1 MB)", + dest="svg_icon", + ) + create_parser.add_argument( + "--python-version", + type=str, + choices=[v.value for v in PythonVersion], + help="Python version for the integration", + dest="python_version", + ) + create_parser.add_argument( + "--integration-type", + type=str, + choices=[t.value for t in IntegrationType], + help="Integration type", + dest="integration_type", + ) + create_parser.set_defaults(func=handle_integration_create_command) + + # download command + download_parser = lvl1.add_parser( + "download", + help="Download an integration package as a ZIP file", + ) + download_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration to download", + dest="integration_id", + required=True, + ) + download_parser.add_argument( + "--output-file", + type=str, + help="Path to write the downloaded ZIP file to", + dest="output_file", + required=True, + ) + download_parser.set_defaults(func=handle_integration_download_command) + + # download-dependency command + download_dep_parser = lvl1.add_parser( + "download-dependency", + help="Download a Python dependency for a custom integration", + ) + download_dep_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration", + dest="integration_id", + required=True, + ) + download_dep_parser.add_argument( + "--dependency-name", + type=str, + help=( + "Dependency name to download. Can include version or " + "repository, e.g. 'requests==2.31.0'" + ), + dest="dependency_name", + required=True, + ) + download_dep_parser.set_defaults( + func=handle_download_integration_dependency_command + ) + + # export-items command + export_items_parser = lvl1.add_parser( + "export-items", + help="Export specific items from an integration as a ZIP file", + ) + export_items_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration to export items from", + dest="integration_id", + required=True, + ) + export_items_parser.add_argument( + "--output-file", + type=str, + help="Path to write the exported ZIP file to", + dest="output_file", + required=True, + ) + export_items_parser.add_argument( + "--actions", + type=str, + nargs="+", + help="IDs of actions to export", + dest="actions", + ) + export_items_parser.add_argument( + "--jobs", + type=str, + nargs="+", + help="IDs of jobs to export", + dest="jobs", + ) + export_items_parser.add_argument( + "--connectors", + type=str, + nargs="+", + help="IDs of connectors to export", + dest="connectors", + ) + export_items_parser.add_argument( + "--managers", + type=str, + nargs="+", + help="IDs of managers to export", + dest="managers", + ) + export_items_parser.add_argument( + "--transformers", + type=str, + nargs="+", + help="IDs of transformers to export", + dest="transformers", + ) + export_items_parser.add_argument( + "--logical-operators", + type=str, + nargs="+", + help="IDs of logical operators to export", + dest="logical_operators", + ) + export_items_parser.set_defaults( + func=handle_export_integration_items_command + ) + + # affected-items command + affected_parser = lvl1.add_parser( + "affected-items", + help="Get items affected by changes to an integration", + ) + affected_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration to check", + dest="integration_id", + required=True, + ) + affected_parser.set_defaults( + func=handle_get_integration_affected_items_command + ) + + # agent-integrations command + agent_parser = lvl1.add_parser( + "agent-integrations", + help="Get integrations installed on a specific agent", + ) + agent_parser.add_argument( + "--agent-id", + type=str, + help="Identifier of the agent", + dest="agent_id", + required=True, + ) + agent_parser.set_defaults(func=handle_get_agent_integrations_command) + + # dependencies command + deps_parser = lvl1.add_parser( + "dependencies", + help="Get Python dependencies for a custom integration", + ) + deps_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration", + dest="integration_id", + required=True, + ) + deps_parser.set_defaults(func=handle_get_integration_dependencies_command) + + # restricted-agents command + restricted_parser = lvl1.add_parser( + "restricted-agents", + help="Get agents restricted from running an updated integration", + ) + restricted_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration", + dest="integration_id", + required=True, + ) + restricted_parser.add_argument( + "--required-python-version", + type=str, + choices=[v.value for v in PythonVersion], + help="Python version required for the updated integration", + dest="required_python_version", + required=True, + ) + restricted_parser.add_argument( + "--push-request", + action="store_true", + help="Indicates the integration is being pushed to a different mode", + dest="push_request", + ) + restricted_parser.set_defaults( + func=handle_get_integration_restricted_agents_command + ) + + # diff command + diff_parser = lvl1.add_parser( + "diff", help="Get the configuration diff for an integration" + ) + diff_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration", + dest="integration_id", + required=True, + ) + diff_parser.add_argument( + "--diff-type", + type=str, + choices=[d.value for d in DiffType], + help=( + "Type of diff to retrieve. " + "COMMERCIAL: diff against the marketplace version. " + "PRODUCTION: diff between staging and production. " + "STAGING: diff between production and staging." + ), + dest="diff_type", + default=DiffType.COMMERCIAL.value, + ) + diff_parser.set_defaults(func=handle_get_integration_diff_command) + + # transition command + transition_parser = lvl1.add_parser( + "transition", + help="Transition an integration to production or staging", + ) + transition_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration to transition", + dest="integration_id", + required=True, + ) + transition_parser.add_argument( + "--target-mode", + type=str, + choices=[t.value for t in TargetMode], + help="Target mode to transition the integration to", + dest="target_mode", + required=True, + ) + transition_parser.set_defaults(func=handle_transition_integration_command) + + # update command + update_parser = lvl1.add_parser( + "update", help="Update an existing integration's metadata" + ) + update_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration to update", + dest="integration_id", + required=True, + ) + update_parser.add_argument( + "--display-name", + type=str, + help="New display name for the integration (max 150 characters)", + dest="display_name", + ) + update_parser.add_argument( + "--description", + type=str, + help="New description for the integration (max 1,500 characters)", + dest="description", + ) + update_parser.add_argument( + "--image-base64", + type=str, + help="New integration image encoded as a base64 string (max 5 MB)", + dest="image_base64", + ) + update_parser.add_argument( + "--svg-icon", + type=str, + help="New integration SVG icon string (max 1 MB)", + dest="svg_icon", + ) + update_parser.add_argument( + "--python-version", + type=str, + choices=[v.value for v in PythonVersion], + help="Python version for the integration", + dest="python_version", + ) + update_parser.add_argument( + "--integration-type", + type=str, + choices=[t.value for t in IntegrationType], + help="Integration type", + dest="integration_type", + ) + update_parser.add_argument( + "--staging", + action="store_true", + help="Set the integration to staging mode", + dest="staging", + ) + update_parser.add_argument( + "--dependencies-to-remove", + type=str, + nargs="+", + help="List of dependency names to remove from the integration", + dest="dependencies_to_remove", + ) + update_parser.add_argument( + "--update-mask", + type=str, + help=( + "Comma-separated list of fields to update. " + "If not provided, all supplied fields are updated." + ), + dest="update_mask", + ) + update_parser.set_defaults(func=handle_update_integration_command) + + # update-custom command + update_custom_parser = lvl1.add_parser( + "update-custom", + help=( + "Update a custom integration definition including " + "parameters and dependencies" + ), + ) + update_custom_parser.add_argument( + "--integration-id", + type=str, + help="ID of the integration to update", + dest="integration_id", + required=True, + ) + update_custom_parser.add_argument( + "--display-name", + type=str, + help="New display name for the integration (max 150 characters)", + dest="display_name", + ) + update_custom_parser.add_argument( + "--description", + type=str, + help="New description for the integration (max 1,500 characters)", + dest="description", + ) + update_custom_parser.add_argument( + "--image-base64", + type=str, + help="New integration image encoded as a base64 string (max 5 MB)", + dest="image_base64", + ) + update_custom_parser.add_argument( + "--svg-icon", + type=str, + help="New integration SVG icon string (max 1 MB)", + dest="svg_icon", + ) + update_custom_parser.add_argument( + "--python-version", + type=str, + choices=[v.value for v in PythonVersion], + help="Python version for the integration", + dest="python_version", + ) + update_custom_parser.add_argument( + "--integration-type", + type=str, + choices=[t.value for t in IntegrationType], + help="Integration type", + dest="integration_type", + ) + update_custom_parser.add_argument( + "--staging", + action="store_true", + help="Set the integration to staging mode", + dest="staging", + ) + update_custom_parser.add_argument( + "--dependencies-to-remove", + type=str, + nargs="+", + help="List of dependency names to remove from the integration", + dest="dependencies_to_remove", + ) + update_custom_parser.add_argument( + "--update-mask", + type=str, + help=( + "Comma-separated list of fields to update. " + "If not provided, all supplied fields are updated." + ), + dest="update_mask", + ) + update_custom_parser.set_defaults( + func=handle_updated_custom_integration_command + ) + + +# --------------------------------------------------------------------------- +# Handlers +# --------------------------------------------------------------------------- + + +def handle_integration_list_command(args, chronicle): + """Handle list integrations command""" + try: + out = chronicle.soar.list_integrations( + page_size=args.page_size, + page_token=args.page_token, + filter_string=args.filter_string, + order_by=args.order_by, + as_list=args.as_list, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error listing integrations: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_integration_get_command(args, chronicle): + """Handle get integration command""" + try: + out = chronicle.soar.get_integration( + integration_name=args.integration_id, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error getting integration: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_integration_delete_command(args, chronicle): + """Handle delete integration command""" + try: + chronicle.soar.delete_integration( + integration_name=args.integration_id, + ) + print(f"Integration {args.integration_id} deleted successfully.") + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error deleting integration: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_integration_create_command(args, chronicle): + """Handle create integration command""" + try: + out = chronicle.soar.create_integration( + display_name=args.display_name, + staging=args.staging, + description=args.description, + image_base64=args.image_base64, + svg_icon=args.svg_icon, + python_version=( + PythonVersion(args.python_version) + if args.python_version + else None + ), + integration_type=( + IntegrationType(args.integration_type) + if args.integration_type + else None + ), + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error creating integration: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_integration_download_command(args, chronicle): + """Handle download integration command""" + try: + zip_bytes = chronicle.download_integration( + integration_name=args.integration_id, + ) + with open(args.output_file, "wb") as f: + f.write(zip_bytes) + print( + f"Integration {args.integration_id} downloaded to " + f"{args.output_file}." + ) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error downloading integration: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_download_integration_dependency_command(args, chronicle): + """Handle download integration dependencies command""" + try: + out = chronicle.soar.download_integration_dependency( + integration_name=args.integration_id, + dependency_name=args.dependency_name, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print( + f"Error downloading integration dependencies: {e}", file=sys.stderr + ) + sys.exit(1) + + +def handle_export_integration_items_command(args, chronicle): + """Handle export integration items command""" + try: + zip_bytes = chronicle.export_integration_items( + integration_name=args.integration_id, + actions=args.actions, + jobs=args.jobs, + connectors=args.connectors, + managers=args.managers, + transformers=args.transformers, + logical_operators=args.logical_operators, + ) + with open(args.output_file, "wb") as f: + f.write(zip_bytes) + print(f"Integration items exported to {args.output_file}.") + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error exporting integration items: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_get_integration_affected_items_command(args, chronicle): + """Handle get integration affected items command""" + try: + out = chronicle.soar.get_integration_affected_items( + integration_name=args.integration_id, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error getting integration affected items: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_get_agent_integrations_command(args, chronicle): + """Handle get agent integration command""" + try: + out = chronicle.soar.get_agent_integrations( + agent_id=args.agent_id, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error getting agent integration: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_get_integration_dependencies_command(args, chronicle): + """Handle get integration dependencies command""" + try: + out = chronicle.soar.get_integration_dependencies( + integration_name=args.integration_id, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error getting integration dependencies: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_get_integration_restricted_agents_command(args, chronicle): + """Handle get integration restricted agent command""" + try: + out = chronicle.soar.get_integration_restricted_agents( + integration_name=args.integration_id, + required_python_version=PythonVersion(args.required_python_version), + push_request=args.push_request, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print( + f"Error getting integration restricted agent: {e}", file=sys.stderr + ) + sys.exit(1) + + +def handle_get_integration_diff_command(args, chronicle): + """Handle get integration diff command""" + try: + out = chronicle.soar.get_integration_diff( + integration_name=args.integration_id, + diff_type=DiffType(args.diff_type), + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error getting integration diff: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_transition_integration_command(args, chronicle): + """Handle transition integration command""" + try: + out = chronicle.soar.transition_integration( + integration_name=args.integration_id, + target_mode=TargetMode(args.target_mode), + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error transitioning integration: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_update_integration_command(args, chronicle): + """Handle update integration command""" + try: + out = chronicle.soar.update_integration( + integration_name=args.integration_id, + display_name=args.display_name, + description=args.description, + image_base64=args.image_base64, + svg_icon=args.svg_icon, + python_version=( + PythonVersion(args.python_version) + if args.python_version + else None + ), + integration_type=( + IntegrationType(args.integration_type) + if args.integration_type + else None + ), + staging=args.staging or None, + dependencies_to_remove=args.dependencies_to_remove, + update_mask=args.update_mask, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error updating integration: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_updated_custom_integration_command(args, chronicle): + """Handle update custom integration command""" + try: + out = chronicle.soar.update_custom_integration( + integration_name=args.integration_id, + display_name=args.display_name, + description=args.description, + image_base64=args.image_base64, + svg_icon=args.svg_icon, + python_version=( + PythonVersion(args.python_version) + if args.python_version + else None + ), + integration_type=( + IntegrationType(args.integration_type) + if args.integration_type + else None + ), + staging=args.staging or None, + dependencies_to_remove=args.dependencies_to_remove, + update_mask=args.update_mask, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error updating custom integration: {e}", file=sys.stderr) + sys.exit(1) diff --git a/src/secops/cli/commands/integration/integration_client.py b/src/secops/cli/commands/integration/integration_client.py new file mode 100644 index 00000000..916c28f3 --- /dev/null +++ b/src/secops/cli/commands/integration/integration_client.py @@ -0,0 +1,74 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Top level arguments for integration commands""" + +from secops.cli.commands.integration import ( + marketplace_integration, + integration, + # actions, + # action_revisions, + # connectors, + # connector_revisions, + # connector_context_properties, + # connector_instance_logs, + # connector_instances, + # jobs, + # job_revisions, + # job_context_properties, + # job_instance_logs, + # job_instances, + # managers, + # manager_revisions, + integration_instances, + # transformers, + # transformer_revisions, + # logical_operators, + # logical_operator_revisions, +) + + +def setup_integrations_command(subparsers): + """Setup integration command""" + integrations_parser = subparsers.add_parser( + "integration", help="Manage SecOps integrations" + ) + lvl1 = integrations_parser.add_subparsers( + dest="integrations_command", help="Integrations command" + ) + + # Setup all subcommands under `integration` + integration.setup_integrations_command(lvl1) + integration_instances.setup_integration_instances_command(lvl1) + # transformers.setup_transformers_command(lvl1) + # transformer_revisions.setup_transformer_revisions_command(lvl1) + # logical_operators.setup_logical_operators_command(lvl1) + # logical_operator_revisions.setup_logical_operator_revisions_command(lvl1) + # actions.setup_actions_command(lvl1) + # action_revisions.setup_action_revisions_command(lvl1) + # connectors.setup_connectors_command(lvl1) + # connector_revisions.setup_connector_revisions_command(lvl1) + # connector_context_properties.setup_connector_context_properties_command( + # lvl1 + # ) + # connector_instance_logs.setup_connector_instance_logs_command(lvl1) + # connector_instances.setup_connector_instances_command(lvl1) + # jobs.setup_jobs_command(lvl1) + # job_revisions.setup_job_revisions_command(lvl1) + # job_context_properties.setup_job_context_properties_command(lvl1) + # job_instance_logs.setup_job_instance_logs_command(lvl1) + # job_instances.setup_job_instances_command(lvl1) + # managers.setup_managers_command(lvl1) + # manager_revisions.setup_manager_revisions_command(lvl1) + marketplace_integration.setup_marketplace_integrations_command(lvl1) diff --git a/src/secops/cli/commands/integration/integration_instances.py b/src/secops/cli/commands/integration/integration_instances.py new file mode 100644 index 00000000..e9e78d26 --- /dev/null +++ b/src/secops/cli/commands/integration/integration_instances.py @@ -0,0 +1,392 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Google SecOps CLI integration instances commands""" + +import json +import sys + +from secops.cli.utils.formatters import output_formatter +from secops.cli.utils.common_args import ( + add_pagination_args, + add_as_list_arg, +) + + +def setup_integration_instances_command(subparsers): + """Setup integration instances command""" + instances_parser = subparsers.add_parser( + "instances", + help="Manage integration instances", + ) + lvl1 = instances_parser.add_subparsers( + dest="integration_instances_command", + help="Integration instances command", + ) + + # list command + list_parser = lvl1.add_parser("list", help="List integration instances") + list_parser.add_argument( + "--integration-name", + type=str, + help="Name of the integration", + dest="integration_name", + required=True, + ) + add_pagination_args(list_parser) + add_as_list_arg(list_parser) + list_parser.add_argument( + "--filter-string", + type=str, + help="Filter string for listing instances", + dest="filter_string", + ) + list_parser.add_argument( + "--order-by", + type=str, + help="Order by string for listing instances", + dest="order_by", + ) + list_parser.set_defaults(func=handle_integration_instances_list_command) + + # get command + get_parser = lvl1.add_parser("get", help="Get integration instance details") + get_parser.add_argument( + "--integration-name", + type=str, + help="Name of the integration", + dest="integration_name", + required=True, + ) + get_parser.add_argument( + "--instance-id", + type=str, + help="ID of the instance to get", + dest="instance_id", + required=True, + ) + get_parser.set_defaults(func=handle_integration_instances_get_command) + + # delete command + delete_parser = lvl1.add_parser( + "delete", help="Delete an integration instance" + ) + delete_parser.add_argument( + "--integration-name", + type=str, + help="Name of the integration", + dest="integration_name", + required=True, + ) + delete_parser.add_argument( + "--instance-id", + type=str, + help="ID of the instance to delete", + dest="instance_id", + required=True, + ) + delete_parser.set_defaults(func=handle_integration_instances_delete_command) + + # create command + create_parser = lvl1.add_parser( + "create", help="Create a new integration instance" + ) + create_parser.add_argument( + "--integration-name", + type=str, + help="Name of the integration", + dest="integration_name", + required=True, + ) + create_parser.add_argument( + "--display-name", + type=str, + help="Display name for the instance", + dest="display_name", + required=True, + ) + create_parser.add_argument( + "--environment", + type=str, + help="Environment name for the instance", + dest="environment", + required=True, + ) + create_parser.add_argument( + "--description", + type=str, + help="Description of the instance", + dest="description", + ) + create_parser.add_argument( + "--instance-id", + type=str, + help="Custom ID for the instance", + dest="instance_id", + ) + create_parser.add_argument( + "--config", + type=str, + help="JSON string of instance configuration", + dest="config", + ) + create_parser.set_defaults(func=handle_integration_instances_create_command) + + # update command + update_parser = lvl1.add_parser( + "update", help="Update an integration instance" + ) + update_parser.add_argument( + "--integration-name", + type=str, + help="Name of the integration", + dest="integration_name", + required=True, + ) + update_parser.add_argument( + "--instance-id", + type=str, + help="ID of the instance to update", + dest="instance_id", + required=True, + ) + update_parser.add_argument( + "--display-name", + type=str, + help="New display name for the instance", + dest="display_name", + ) + update_parser.add_argument( + "--description", + type=str, + help="New description for the instance", + dest="description", + ) + update_parser.add_argument( + "--config", + type=str, + help="JSON string of new instance configuration", + dest="config", + ) + update_parser.add_argument( + "--update-mask", + type=str, + help="Comma-separated list of fields to update", + dest="update_mask", + ) + update_parser.set_defaults(func=handle_integration_instances_update_command) + + # test command + test_parser = lvl1.add_parser( + "test", help="Execute an integration instance test" + ) + test_parser.add_argument( + "--integration-name", + type=str, + help="Name of the integration", + dest="integration_name", + required=True, + ) + test_parser.add_argument( + "--instance-id", + type=str, + help="ID of the instance to test", + dest="instance_id", + required=True, + ) + test_parser.set_defaults(func=handle_integration_instances_test_command) + + # get-affected-items command + affected_parser = lvl1.add_parser( + "get-affected-items", + help="Get items affected by an integration instance", + ) + affected_parser.add_argument( + "--integration-name", + type=str, + help="Name of the integration", + dest="integration_name", + required=True, + ) + affected_parser.add_argument( + "--instance-id", + type=str, + help="ID of the instance", + dest="instance_id", + required=True, + ) + affected_parser.set_defaults( + func=handle_integration_instances_get_affected_items_command + ) + + # get-default command + default_parser = lvl1.add_parser( + "get-default", + help="Get the default integration instance", + ) + default_parser.add_argument( + "--integration-name", + type=str, + help="Name of the integration", + dest="integration_name", + required=True, + ) + default_parser.set_defaults( + func=handle_integration_instances_get_default_command + ) + + +def handle_integration_instances_list_command(args, chronicle): + """Handle integration instances list command""" + try: + out = chronicle.soar.list_integration_instances( + integration_name=args.integration_name, + page_size=args.page_size, + page_token=args.page_token, + filter_string=args.filter_string, + order_by=args.order_by, + as_list=args.as_list, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error listing integration instances: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_integration_instances_get_command(args, chronicle): + """Handle integration instance get command""" + try: + out = chronicle.soar.get_integration_instance( + integration_name=args.integration_name, + integration_instance_id=args.instance_id, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error getting integration instance: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_integration_instances_delete_command(args, chronicle): + """Handle integration instance delete command""" + try: + chronicle.soar.delete_integration_instance( + integration_name=args.integration_name, + integration_instance_id=args.instance_id, + ) + print(f"Integration instance {args.instance_id} deleted successfully") + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error deleting integration instance: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_integration_instances_create_command(args, chronicle): + """Handle integration instance create command""" + try: + # Parse config if provided + + config = None + if args.config: + config = json.loads(args.config) + + out = chronicle.soar.create_integration_instance( + integration_name=args.integration_name, + display_name=args.display_name, + environment=args.environment, + description=args.description, + integration_instance_id=args.instance_id, + config=config, + ) + output_formatter(out, getattr(args, "output", "json")) + except json.JSONDecodeError as e: + print(f"Error parsing config JSON: {e}", file=sys.stderr) + sys.exit(1) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error creating integration instance: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_integration_instances_update_command(args, chronicle): + """Handle integration instance update command""" + try: + # Parse config if provided + + config = None + if args.config: + config = json.loads(args.config) + + out = chronicle.soar.update_integration_instance( + integration_name=args.integration_name, + integration_instance_id=args.instance_id, + display_name=args.display_name, + description=args.description, + config=config, + update_mask=args.update_mask, + ) + output_formatter(out, getattr(args, "output", "json")) + except json.JSONDecodeError as e: + print(f"Error parsing config JSON: {e}", file=sys.stderr) + sys.exit(1) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error updating integration instance: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_integration_instances_test_command(args, chronicle): + """Handle integration instance test command""" + try: + # Get the instance first + instance = chronicle.soar.get_integration_instance( + integration_name=args.integration_name, + integration_instance_id=args.instance_id, + ) + + out = chronicle.soar.execute_integration_instance_test( + integration_name=args.integration_name, + integration_instance_id=args.instance_id, + integration_instance=instance, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error testing integration instance: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_integration_instances_get_affected_items_command(args, chronicle): + """Handle get integration instance affected items command""" + try: + out = chronicle.soar.get_integration_instance_affected_items( + integration_name=args.integration_name, + integration_instance_id=args.instance_id, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print( + f"Error getting integration instance affected items: {e}", + file=sys.stderr, + ) + sys.exit(1) + + +def handle_integration_instances_get_default_command(args, chronicle): + """Handle get default integration instance command""" + try: + out = chronicle.soar.get_default_integration_instance( + integration_name=args.integration_name, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print( + f"Error getting default integration instance: {e}", file=sys.stderr + ) + sys.exit(1) diff --git a/src/secops/cli/commands/integration/marketplace_integration.py b/src/secops/cli/commands/integration/marketplace_integration.py new file mode 100644 index 00000000..5ce0467c --- /dev/null +++ b/src/secops/cli/commands/integration/marketplace_integration.py @@ -0,0 +1,204 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Google SecOps CLI marketplace integration commands""" + +import sys + +from secops.cli.utils.formatters import output_formatter +from secops.cli.utils.common_args import ( + add_pagination_args, + add_as_list_arg, +) + + +def setup_marketplace_integrations_command(subparsers): + """Setup marketplace integration command""" + mp_parser = subparsers.add_parser( + "marketplace", + help="Manage Chronicle marketplace integration", + ) + lvl1 = mp_parser.add_subparsers( + dest="mp_command", help="Marketplace integration command" + ) + + # list command + list_parser = lvl1.add_parser("list", help="List marketplace integrations") + add_pagination_args(list_parser) + add_as_list_arg(list_parser) + list_parser.add_argument( + "--filter-string", + type=str, + help="Filter string for listing marketplace integrations", + dest="filter_string", + ) + list_parser.add_argument( + "--order-by", + type=str, + help="Order by string for listing marketplace integrations", + dest="order_by", + ) + list_parser.set_defaults(func=handle_mp_integration_list_command) + + # get command + get_parser = lvl1.add_parser( + "get", help="Get marketplace integration details" + ) + get_parser.add_argument( + "--integration-name", + type=str, + help="Name of the marketplace integration to get", + dest="integration_name", + required=True, + ) + get_parser.set_defaults(func=handle_mp_integration_get_command) + + # diff command + diff_parser = lvl1.add_parser( + "diff", + help="Get marketplace integration diff between " + "installed and latest version", + ) + diff_parser.add_argument( + "--integration-name", + type=str, + help="Name of the marketplace integration to diff", + dest="integration_name", + required=True, + ) + diff_parser.set_defaults(func=handle_mp_integration_diff_command) + + # install command + install_parser = lvl1.add_parser( + "install", help="Install or update a marketplace integration" + ) + install_parser.add_argument( + "--integration-name", + type=str, + help="Name of the marketplace integration to install or update", + dest="integration_name", + required=True, + ) + install_parser.add_argument( + "--override-mapping", + action="store_true", + help="Override existing mapping", + dest="override_mapping", + ) + install_parser.add_argument( + "--staging", + action="store_true", + help="Whether to install the integration in " + "staging environment (true/false)", + dest="staging", + ) + install_parser.add_argument( + "--version", + type=str, + help="Version of the marketplace integration to install", + dest="version", + ) + install_parser.add_argument( + "--restore-from-snapshot", + action="store_true", + help="Whether to restore the integration from existing snapshot " + "(true/false)", + dest="restore_from_snapshot", + ) + install_parser.set_defaults(func=handle_mp_integration_install_command) + + # uninstall command + uninstall_parser = lvl1.add_parser( + "uninstall", help="Uninstall a marketplace integration" + ) + uninstall_parser.add_argument( + "--integration-name", + type=str, + help="Name of the marketplace integration to uninstall", + dest="integration_name", + required=True, + ) + uninstall_parser.set_defaults(func=handle_mp_integration_uninstall_command) + + +def handle_mp_integration_list_command(args, chronicle): + """Handle marketplace integration list command""" + try: + out = chronicle.soar.list_marketplace_integrations( + page_size=args.page_size, + page_token=args.page_token, + filter_string=args.filter_string, + order_by=args.order_by, + as_list=args.as_list, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error listing marketplace integrations: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_mp_integration_get_command(args, chronicle): + """Handle marketplace integration get command""" + try: + out = chronicle.soar.get_marketplace_integration( + integration_name=args.integration_name, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error getting marketplace integration: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_mp_integration_diff_command(args, chronicle): + """Handle marketplace integration diff command""" + try: + out = chronicle.soar.get_marketplace_integration_diff( + integration_name=args.integration_name, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print( + f"Error getting marketplace integration diff: {e}", file=sys.stderr + ) + sys.exit(1) + + +def handle_mp_integration_install_command(args, chronicle): + """Handle marketplace integration install command""" + try: + out = chronicle.soar.install_marketplace_integration( + integration_name=args.integration_name, + override_mapping=args.override_mapping, + staging=args.staging, + version=args.version, + restore_from_snapshot=args.restore_from_snapshot, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print(f"Error installing marketplace integration: {e}", file=sys.stderr) + sys.exit(1) + + +def handle_mp_integration_uninstall_command(args, chronicle): + """Handle marketplace integration uninstall command""" + try: + out = chronicle.soar.uninstall_marketplace_integration( + integration_name=args.integration_name, + ) + output_formatter(out, getattr(args, "output", "json")) + except Exception as e: # pylint: disable=broad-exception-caught + print( + f"Error uninstalling marketplace integration: {e}", file=sys.stderr + ) + sys.exit(1) diff --git a/tests/chronicle/soar/__init__.py b/tests/chronicle/soar/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/chronicle/soar/integration/test_integration_instances.py b/tests/chronicle/soar/integration/test_integration_instances.py new file mode 100644 index 00000000..fa65ecad --- /dev/null +++ b/tests/chronicle/soar/integration/test_integration_instances.py @@ -0,0 +1,623 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Tests for Chronicle marketplace integration instances functions.""" + +from unittest.mock import Mock, patch + +import pytest + +from secops.chronicle.client import ChronicleClient +from secops.chronicle.models import ( + APIVersion, + IntegrationInstanceParameter, +) +from secops.chronicle.soar.integration.integration_instances import ( + list_integration_instances, + get_integration_instance, + delete_integration_instance, + create_integration_instance, + update_integration_instance, + execute_integration_instance_test, + get_integration_instance_affected_items, + get_default_integration_instance, +) +from secops.exceptions import APIError + + +@pytest.fixture +def chronicle_client(): + """Create a Chronicle client for testing.""" + with patch("secops.auth.SecOpsAuth") as mock_auth: + mock_session = Mock() + mock_session.headers = {} + mock_auth.return_value.session = mock_session + return ChronicleClient( + customer_id="test-customer", + project_id="test-project", + default_api_version=APIVersion.V1BETA, + ) + + +# -- list_integration_instances tests -- + + +def test_list_integration_instances_success(chronicle_client): + """Test list_integration_instances delegates to chronicle_paginated_request.""" + expected = { + "integrationInstances": [{"name": "ii1"}, {"name": "ii2"}], + "nextPageToken": "t", + } + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated, patch( + "secops.chronicle.soar.integration.integration_instances.format_resource_id", + return_value="My Integration", + ): + result = list_integration_instances( + chronicle_client, + integration_name="My Integration", + page_size=10, + page_token="next-token", + ) + + assert result == expected + + _, kwargs = mock_paginated.call_args + assert "integrationInstances" in kwargs["path"] + assert kwargs["items_key"] == "integrationInstances" + assert kwargs["page_size"] == 10 + assert kwargs["page_token"] == "next-token" + + +def test_list_integration_instances_default_args(chronicle_client): + """Test list_integration_instances with default args.""" + expected = {"integrationInstances": []} + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_paginated_request", + return_value=expected, + ): + result = list_integration_instances( + chronicle_client, + integration_name="test-integration", + ) + + assert result == expected + + +def test_list_integration_instances_with_filters(chronicle_client): + """Test list_integration_instances with filter and order_by.""" + expected = {"integrationInstances": [{"name": "ii1"}]} + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated: + result = list_integration_instances( + chronicle_client, + integration_name="test-integration", + filter_string="environment = 'prod'", + order_by="displayName", + ) + + assert result == expected + + _, kwargs = mock_paginated.call_args + assert kwargs["extra_params"] == { + "filter": "environment = 'prod'", + "orderBy": "displayName", + } + + +def test_list_integration_instances_as_list(chronicle_client): + """Test list_integration_instances returns list when as_list=True.""" + expected = [{"name": "ii1"}, {"name": "ii2"}] + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated: + result = list_integration_instances( + chronicle_client, + integration_name="test-integration", + as_list=True, + ) + + assert result == expected + + _, kwargs = mock_paginated.call_args + assert kwargs["as_list"] is True + + +def test_list_integration_instances_error(chronicle_client): + """Test list_integration_instances raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_paginated_request", + side_effect=APIError("Failed to list integration instances"), + ): + with pytest.raises(APIError) as exc_info: + list_integration_instances( + chronicle_client, + integration_name="test-integration", + ) + assert "Failed to list integration instances" in str(exc_info.value) + + +# -- get_integration_instance tests -- + + +def test_get_integration_instance_success(chronicle_client): + """Test get_integration_instance issues GET request.""" + expected = { + "name": "integrationInstances/ii1", + "displayName": "My Instance", + "environment": "production", + } + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_integration_instance( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + ) + + assert result == expected + + _, kwargs = mock_request.call_args + assert kwargs["method"] == "GET" + assert "integrationInstances/ii1" in kwargs["endpoint_path"] + assert kwargs["api_version"] == APIVersion.V1BETA + + +def test_get_integration_instance_error(chronicle_client): + """Test get_integration_instance raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + side_effect=APIError("Failed to get integration instance"), + ): + with pytest.raises(APIError) as exc_info: + get_integration_instance( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + ) + assert "Failed to get integration instance" in str(exc_info.value) + + +# -- delete_integration_instance tests -- + + +def test_delete_integration_instance_success(chronicle_client): + """Test delete_integration_instance issues DELETE request.""" + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=None, + ) as mock_request: + delete_integration_instance( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + ) + + _, kwargs = mock_request.call_args + assert kwargs["method"] == "DELETE" + assert "integrationInstances/ii1" in kwargs["endpoint_path"] + assert kwargs["api_version"] == APIVersion.V1BETA + + +def test_delete_integration_instance_error(chronicle_client): + """Test delete_integration_instance raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + side_effect=APIError("Failed to delete integration instance"), + ): + with pytest.raises(APIError) as exc_info: + delete_integration_instance( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + ) + assert "Failed to delete integration instance" in str(exc_info.value) + + +# -- create_integration_instance tests -- + + +def test_create_integration_instance_required_fields_only(chronicle_client): + """Test create_integration_instance sends only required fields.""" + expected = {"name": "integrationInstances/new", "displayName": "My Instance"} + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = create_integration_instance( + chronicle_client, + integration_name="test-integration", + environment="production", + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="integrations/test-integration/integrationInstances", + api_version=APIVersion.V1BETA, + json={ + "environment": "production", + }, + ) + + +def test_create_integration_instance_with_optional_fields(chronicle_client): + """Test create_integration_instance includes optional fields when provided.""" + expected = {"name": "integrationInstances/new"} + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = create_integration_instance( + chronicle_client, + integration_name="test-integration", + environment="production", + display_name="My Instance", + description="Test instance", + parameters=[{"id": 1, "value": "test"}], + agent="agent-123", + ) + + assert result == expected + + _, kwargs = mock_request.call_args + assert kwargs["json"]["environment"] == "production" + assert kwargs["json"]["displayName"] == "My Instance" + assert kwargs["json"]["description"] == "Test instance" + assert kwargs["json"]["parameters"] == [{"id": 1, "value": "test"}] + assert kwargs["json"]["agent"] == "agent-123" + + +def test_create_integration_instance_with_dataclass_params(chronicle_client): + """Test create_integration_instance converts dataclass parameters.""" + expected = {"name": "integrationInstances/new"} + + param = IntegrationInstanceParameter(value="test-value") + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = create_integration_instance( + chronicle_client, + integration_name="test-integration", + environment="production", + parameters=[param], + ) + + assert result == expected + + _, kwargs = mock_request.call_args + params_sent = kwargs["json"]["parameters"] + assert len(params_sent) == 1 + assert params_sent[0]["value"] == "test-value" + + +def test_create_integration_instance_error(chronicle_client): + """Test create_integration_instance raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + side_effect=APIError("Failed to create integration instance"), + ): + with pytest.raises(APIError) as exc_info: + create_integration_instance( + chronicle_client, + integration_name="test-integration", + environment="production", + ) + assert "Failed to create integration instance" in str(exc_info.value) + + +# -- update_integration_instance tests -- + + +def test_update_integration_instance_with_single_field(chronicle_client): + """Test update_integration_instance with single field updates updateMask.""" + expected = {"name": "integrationInstances/ii1", "displayName": "Updated"} + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = update_integration_instance( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + display_name="Updated", + ) + + assert result == expected + + _, kwargs = mock_request.call_args + assert kwargs["method"] == "PATCH" + assert "integrationInstances/ii1" in kwargs["endpoint_path"] + assert kwargs["json"]["displayName"] == "Updated" + assert kwargs["params"]["updateMask"] == "displayName" + + +def test_update_integration_instance_with_multiple_fields(chronicle_client): + """Test update_integration_instance with multiple fields.""" + expected = {"name": "integrationInstances/ii1"} + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = update_integration_instance( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + display_name="Updated", + description="New description", + environment="staging", + ) + + assert result == expected + + _, kwargs = mock_request.call_args + assert kwargs["json"]["displayName"] == "Updated" + assert kwargs["json"]["description"] == "New description" + assert kwargs["json"]["environment"] == "staging" + assert "displayName" in kwargs["params"]["updateMask"] + assert "description" in kwargs["params"]["updateMask"] + assert "environment" in kwargs["params"]["updateMask"] + + +def test_update_integration_instance_with_custom_update_mask(chronicle_client): + """Test update_integration_instance with explicitly provided update_mask.""" + expected = {"name": "integrationInstances/ii1"} + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = update_integration_instance( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + display_name="Updated", + update_mask="displayName,environment", + ) + + assert result == expected + + _, kwargs = mock_request.call_args + assert kwargs["params"]["updateMask"] == "displayName,environment" + + +def test_update_integration_instance_with_dataclass_params(chronicle_client): + """Test update_integration_instance converts dataclass parameters.""" + expected = {"name": "integrationInstances/ii1"} + + param = IntegrationInstanceParameter(value="test-value") + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = update_integration_instance( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + parameters=[param], + ) + + assert result == expected + + _, kwargs = mock_request.call_args + params_sent = kwargs["json"]["parameters"] + assert len(params_sent) == 1 + assert params_sent[0]["value"] == "test-value" + + +def test_update_integration_instance_error(chronicle_client): + """Test update_integration_instance raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + side_effect=APIError("Failed to update integration instance"), + ): + with pytest.raises(APIError) as exc_info: + update_integration_instance( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + display_name="Updated", + ) + assert "Failed to update integration instance" in str(exc_info.value) + + +# -- execute_integration_instance_test tests -- + + +def test_execute_integration_instance_test_success(chronicle_client): + """Test execute_integration_instance_test issues POST request.""" + expected = { + "successful": True, + "message": "Test successful", + } + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = execute_integration_instance_test( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + ) + + assert result == expected + + _, kwargs = mock_request.call_args + assert kwargs["method"] == "POST" + assert "integrationInstances/ii1:executeTest" in kwargs["endpoint_path"] + assert kwargs["api_version"] == APIVersion.V1BETA + + +def test_execute_integration_instance_test_failure(chronicle_client): + """Test execute_integration_instance_test when test fails.""" + expected = { + "successful": False, + "message": "Connection failed", + } + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ): + result = execute_integration_instance_test( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + ) + + assert result == expected + assert result["successful"] is False + + +def test_execute_integration_instance_test_error(chronicle_client): + """Test execute_integration_instance_test raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + side_effect=APIError("Failed to execute test"), + ): + with pytest.raises(APIError) as exc_info: + execute_integration_instance_test( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + ) + assert "Failed to execute test" in str(exc_info.value) + + +# -- get_integration_instance_affected_items tests -- + + +def test_get_integration_instance_affected_items_success(chronicle_client): + """Test get_integration_instance_affected_items issues GET request.""" + expected = { + "affectedPlaybooks": [ + {"name": "playbook1", "displayName": "Playbook 1"}, + {"name": "playbook2", "displayName": "Playbook 2"}, + ] + } + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_integration_instance_affected_items( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + ) + + assert result == expected + + _, kwargs = mock_request.call_args + assert kwargs["method"] == "GET" + assert "integrationInstances/ii1:fetchAffectedItems" in kwargs["endpoint_path"] + assert kwargs["api_version"] == APIVersion.V1BETA + + +def test_get_integration_instance_affected_items_empty(chronicle_client): + """Test get_integration_instance_affected_items with no affected items.""" + expected = {"affectedPlaybooks": []} + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ): + result = get_integration_instance_affected_items( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + ) + + assert result == expected + assert len(result["affectedPlaybooks"]) == 0 + + +def test_get_integration_instance_affected_items_error(chronicle_client): + """Test get_integration_instance_affected_items raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + side_effect=APIError("Failed to fetch affected items"), + ): + with pytest.raises(APIError) as exc_info: + get_integration_instance_affected_items( + chronicle_client, + integration_name="test-integration", + integration_instance_id="ii1", + ) + assert "Failed to fetch affected items" in str(exc_info.value) + + +# -- get_default_integration_instance tests -- + + +def test_get_default_integration_instance_success(chronicle_client): + """Test get_default_integration_instance issues GET request.""" + expected = { + "name": "integrationInstances/default", + "displayName": "Default Instance", + "environment": "default", + } + + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_default_integration_instance( + chronicle_client, + integration_name="test-integration", + ) + + assert result == expected + + _, kwargs = mock_request.call_args + assert kwargs["method"] == "GET" + assert "integrationInstances:fetchDefaultInstance" in kwargs["endpoint_path"] + assert kwargs["api_version"] == APIVersion.V1BETA + + +def test_get_default_integration_instance_error(chronicle_client): + """Test get_default_integration_instance raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integration_instances.chronicle_request", + side_effect=APIError("Failed to get default instance"), + ): + with pytest.raises(APIError) as exc_info: + get_default_integration_instance( + chronicle_client, + integration_name="test-integration", + ) + assert "Failed to get default instance" in str(exc_info.value) + diff --git a/tests/chronicle/soar/integration/test_integrations.py b/tests/chronicle/soar/integration/test_integrations.py new file mode 100644 index 00000000..0c508260 --- /dev/null +++ b/tests/chronicle/soar/integration/test_integrations.py @@ -0,0 +1,909 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Tests for Chronicle integration functions.""" + +from unittest.mock import Mock, patch + +import pytest + +from secops.chronicle.client import ChronicleClient +from secops.chronicle.models import ( + APIVersion, + DiffType, + TargetMode, + PythonVersion, +) +from secops.chronicle.soar.integration.integrations import ( + list_integrations, + get_integration, + delete_integration, + create_integration, + download_integration, + download_integration_dependency, + export_integration_items, + get_integration_affected_items, + get_agent_integrations, + get_integration_dependencies, + get_integration_restricted_agents, + get_integration_diff, + transition_integration, + update_integration, + update_custom_integration, +) +from secops.exceptions import APIError + + +@pytest.fixture +def chronicle_client(): + """Create a Chronicle client for testing.""" + with patch("secops.auth.SecOpsAuth") as mock_auth: + mock_session = Mock() + mock_session.headers = {} + mock_auth.return_value.session = mock_session + return ChronicleClient( + customer_id="test-customer", + project_id="test-project", + default_api_version=APIVersion.V1BETA, + ) + + +@pytest.fixture +def mock_response() -> Mock: + """Create a mock API response object.""" + mock = Mock() + mock.status_code = 200 + mock.json.return_value = {} + return mock + + +@pytest.fixture +def mock_error_response() -> Mock: + """Create a mock error API response object.""" + mock = Mock() + mock.status_code = 400 + mock.text = "Error message" + mock.raise_for_status.side_effect = Exception("API Error") + return mock + + +# -- list_integrations tests -- + + +def test_list_integrations_success(chronicle_client): + """Test list_integrations delegates to chronicle_paginated_request.""" + expected = {"integrations": [{"name": "i1"}, {"name": "i2"}]} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated: + result = list_integrations( + chronicle_client, + page_size=10, + page_token="next-token", + ) + + assert result == expected + + mock_paginated.assert_called_once_with( + chronicle_client, + api_version=APIVersion.V1BETA, + path="integrations", + items_key="integrations", + page_size=10, + page_token="next-token", + extra_params={}, + as_list=False, + ) + + +def test_list_integrations_with_filter_and_order_by(chronicle_client): + """Test list_integrations passes filter_string and order_by in extra_params.""" + expected = {"integrations": []} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated: + result = list_integrations( + chronicle_client, + filter_string='displayName = "My Integration"', + order_by="displayName", + as_list=True, + ) + + assert result == expected + + mock_paginated.assert_called_once_with( + chronicle_client, + api_version=APIVersion.V1BETA, + path="integrations", + items_key="integrations", + page_size=None, + page_token=None, + extra_params={ + "filter": 'displayName = "My Integration"', + "orderBy": "displayName", + }, + as_list=True, + ) + + +def test_list_integrations_error(chronicle_client): + """Test list_integrations propagates APIError from helper.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_paginated_request", + side_effect=APIError("Failed to list integrations"), + ): + with pytest.raises(APIError) as exc_info: + list_integrations(chronicle_client) + + assert "Failed to list integrations" in str(exc_info.value) + + +# -- get_integration tests -- + + +def test_get_integration_success(chronicle_client): + """Test get_integration returns expected result.""" + expected = {"name": "integrations/test-integration", "displayName": "Test"} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_integration(chronicle_client, "test-integration") + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="integrations/test-integration", + api_version=APIVersion.V1BETA, + ) + + +def test_get_integration_error(chronicle_client): + """Test get_integration raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to get integration"), + ): + with pytest.raises(APIError) as exc_info: + get_integration(chronicle_client, "test-integration") + + assert "Failed to get integration" in str(exc_info.value) + + +# -- delete_integration tests -- + + +def test_delete_integration_success(chronicle_client): + """Test delete_integration delegates to chronicle_request.""" + with patch("secops.chronicle.soar.integration.integrations.chronicle_request") as mock_request: + delete_integration(chronicle_client, "test-integration") + + mock_request.assert_called_once_with( + chronicle_client, + method="DELETE", + endpoint_path="integrations/test-integration", + api_version=APIVersion.V1BETA, + ) + + +def test_delete_integration_error(chronicle_client): + """Test delete_integration propagates APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to delete integration"), + ): + with pytest.raises(APIError) as exc_info: + delete_integration(chronicle_client, "test-integration") + + assert "Failed to delete integration" in str(exc_info.value) + + +# -- create_integration tests -- + + +def test_create_integration_required_fields_only(chronicle_client): + """Test create_integration with required fields only.""" + expected = {"name": "integrations/test", "displayName": "Test"} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = create_integration( + chronicle_client, + display_name="Test", + staging=True, + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="integrations", + json={"displayName": "Test", "staging": True}, + api_version=APIVersion.V1BETA, + ) + + +def test_create_integration_all_optional_fields(chronicle_client): + """Test create_integration with all optional fields.""" + expected = {"name": "integrations/test"} + + python_version = list(PythonVersion)[0] + integration_type = Mock(name="integration_type") + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = create_integration( + chronicle_client, + display_name="Test", + staging=False, + description="desc", + image_base64="b64", + svg_icon="", + python_version=python_version, + parameters=[{"id": "p1"}], + categories=["cat"], + integration_type=integration_type, + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="integrations", + json={ + "displayName": "Test", + "staging": False, + "description": "desc", + "imageBase64": "b64", + "svgIcon": "", + "pythonVersion": python_version, + "parameters": [{"id": "p1"}], + "categories": ["cat"], + "type": integration_type, + }, + api_version=APIVersion.V1BETA, + ) + + +def test_create_integration_none_fields_excluded(chronicle_client): + """Test that None optional fields are excluded from create_integration body.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value={"name": "integrations/test"}, + ) as mock_request: + create_integration( + chronicle_client, + display_name="Test", + staging=True, + description=None, + image_base64=None, + svg_icon=None, + python_version=None, + parameters=None, + categories=None, + integration_type=None, + ) + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="integrations", + json={"displayName": "Test", "staging": True}, + api_version=APIVersion.V1BETA, + ) + + +def test_create_integration_error(chronicle_client): + """Test create_integration raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to create integration"), + ): + with pytest.raises(APIError) as exc_info: + create_integration(chronicle_client, display_name="Test", staging=True) + + assert "Failed to create integration" in str(exc_info.value) + + +# -- download_integration tests -- + + +def test_download_integration_success(chronicle_client): + """Test download_integration uses chronicle_request_bytes with alt=media and zip accept.""" + expected = b"ZIPBYTES" + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request_bytes", + return_value=expected, + ) as mock_bytes: + result = download_integration(chronicle_client, "test-integration") + + assert result == expected + + mock_bytes.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="integrations/test-integration:export", + api_version=APIVersion.V1BETA, + params={"alt": "media"}, + headers={"Accept": "application/zip"}, + ) + + +def test_download_integration_error(chronicle_client): + """Test download_integration propagates APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request_bytes", + side_effect=APIError("Failed to download integration"), + ): + with pytest.raises(APIError) as exc_info: + download_integration(chronicle_client, "test-integration") + + assert "Failed to download integration" in str(exc_info.value) + + +# -- download_integration_dependency tests -- + + +def test_download_integration_dependency_success(chronicle_client): + """Test download_integration_dependency posts dependency name.""" + expected = {"dependency": "requests"} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = download_integration_dependency( + chronicle_client, + integration_name="test-integration", + dependency_name="requests==2.32.0", + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="integrations/test-integration:downloadDependency", + json={"dependency": "requests==2.32.0"}, + api_version=APIVersion.V1BETA, + ) + + +def test_download_integration_dependency_error(chronicle_client): + """Test download_integration_dependency raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to download dependency"), + ): + with pytest.raises(APIError) as exc_info: + download_integration_dependency( + chronicle_client, + integration_name="test-integration", + dependency_name="requests", + ) + + assert "Failed to download dependency" in str(exc_info.value) + + +# -- export_integration_items tests -- + + +def test_export_integration_items_success_some_fields(chronicle_client): + """Test export_integration_items builds params correctly and uses chronicle_request_bytes.""" + expected = b"ZIPBYTES" + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request_bytes", + return_value=expected, + ) as mock_bytes: + result = export_integration_items( + chronicle_client, + integration_name="test-integration", + actions=["1", "2"], + connectors=["10"], + logical_operators=["7"], + ) + + assert result == expected + + mock_bytes.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="integrations/test-integration:exportItems", + params={ + "actions": "1,2", + "connectors": ["10"], + "logicalOperators": ["7"], + "alt": "media", + }, + api_version=APIVersion.V1BETA, + headers={"Accept": "application/zip"}, + ) + + +def test_export_integration_items_no_fields(chronicle_client): + """Test export_integration_items always includes alt=media.""" + expected = b"ZIPBYTES" + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request_bytes", + return_value=expected, + ) as mock_bytes: + result = export_integration_items( + chronicle_client, + integration_name="test-integration", + ) + + assert result == expected + + mock_bytes.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="integrations/test-integration:exportItems", + params={"alt": "media"}, + api_version=APIVersion.V1BETA, + headers={"Accept": "application/zip"}, + ) + + +def test_export_integration_items_error(chronicle_client): + """Test export_integration_items propagates APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request_bytes", + side_effect=APIError("Failed to export integration items"), + ): + with pytest.raises(APIError) as exc_info: + export_integration_items(chronicle_client, "test-integration") + + assert "Failed to export integration items" in str(exc_info.value) + + +# -- get_integration_affected_items tests -- + + +def test_get_integration_affected_items_success(chronicle_client): + """Test get_integration_affected_items delegates to chronicle_request.""" + expected = {"affectedItems": []} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_integration_affected_items(chronicle_client, "test-integration") + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="integrations/test-integration:fetchAffectedItems", + api_version=APIVersion.V1BETA, + ) + + +def test_get_integration_affected_items_error(chronicle_client): + """Test get_integration_affected_items raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to fetch affected items"), + ): + with pytest.raises(APIError) as exc_info: + get_integration_affected_items(chronicle_client, "test-integration") + + assert "Failed to fetch affected items" in str(exc_info.value) + + +# -- get_agent_integrations tests -- + + +def test_get_agent_integrations_success(chronicle_client): + """Test get_agent_integrations passes agentId parameter.""" + expected = {"integrations": []} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_agent_integrations(chronicle_client, agent_id="agent-123") + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="integrations:fetchAgentIntegrations", + params={"agentId": "agent-123"}, + api_version=APIVersion.V1BETA, + ) + + +def test_get_agent_integrations_error(chronicle_client): + """Test get_agent_integrations raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to fetch agent integrations"), + ): + with pytest.raises(APIError) as exc_info: + get_agent_integrations(chronicle_client, agent_id="agent-123") + + assert "Failed to fetch agent integrations" in str(exc_info.value) + + +# -- get_integration_dependencies tests -- + + +def test_get_integration_dependencies_success(chronicle_client): + """Test get_integration_dependencies delegates to chronicle_request.""" + expected = {"dependencies": []} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_integration_dependencies(chronicle_client, "test-integration") + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="integrations/test-integration:fetchDependencies", + api_version=APIVersion.V1BETA, + ) + + +def test_get_integration_dependencies_error(chronicle_client): + """Test get_integration_dependencies raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to fetch dependencies"), + ): + with pytest.raises(APIError) as exc_info: + get_integration_dependencies(chronicle_client, "test-integration") + + assert "Failed to fetch dependencies" in str(exc_info.value) + + +# -- get_integration_restricted_agents tests -- + + +def test_get_integration_restricted_agents_success(chronicle_client): + """Test get_integration_restricted_agents passes required python version and pushRequest.""" + expected = {"restrictedAgents": []} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_integration_restricted_agents( + chronicle_client, + integration_name="test-integration", + required_python_version=PythonVersion.PYTHON_3_11, + push_request=True, + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="integrations/test-integration:fetchRestrictedAgents", + params={ + "requiredPythonVersion": PythonVersion.PYTHON_3_11.value, + "pushRequest": True, + }, + api_version=APIVersion.V1BETA, + ) + + +def test_get_integration_restricted_agents_default_push_request(chronicle_client): + """Test get_integration_restricted_agents default push_request=False is sent.""" + expected = {"restrictedAgents": []} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + get_integration_restricted_agents( + chronicle_client, + integration_name="test-integration", + required_python_version=PythonVersion.PYTHON_3_11, + ) + + mock_request.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="integrations/test-integration:fetchRestrictedAgents", + params={ + "requiredPythonVersion": PythonVersion.PYTHON_3_11.value, + "pushRequest": False, + }, + api_version=APIVersion.V1BETA, + ) + + +def test_get_integration_restricted_agents_error(chronicle_client): + """Test get_integration_restricted_agents raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to fetch restricted agents"), + ): + with pytest.raises(APIError) as exc_info: + get_integration_restricted_agents( + chronicle_client, + integration_name="test-integration", + required_python_version=PythonVersion.PYTHON_3_11, + ) + + assert "Failed to fetch restricted agents" in str(exc_info.value) + + +# -- get_integration_diff tests -- + + +def test_get_integration_diff_success(chronicle_client): + """Test get_integration_diff builds endpoint with diff type.""" + expected = {"diff": {}} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_integration_diff( + chronicle_client, + integration_name="test-integration", + diff_type=DiffType.PRODUCTION, + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="integrations/test-integration:fetchProductionDiff", + api_version=APIVersion.V1BETA, + ) + + +def test_get_integration_diff_error(chronicle_client): + """Test get_integration_diff raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to fetch diff"), + ): + with pytest.raises(APIError) as exc_info: + get_integration_diff(chronicle_client, "test-integration") + + assert "Failed to fetch diff" in str(exc_info.value) + + +# -- transition_integration tests -- + + +def test_transition_integration_success(chronicle_client): + """Test transition_integration posts to pushTo{TargetMode}.""" + expected = {"name": "integrations/test"} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = transition_integration( + chronicle_client, + integration_name="test-integration", + target_mode=TargetMode.PRODUCTION, + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="integrations/test-integration:pushToProduction", + api_version=APIVersion.V1BETA, + ) + + +def test_transition_integration_error(chronicle_client): + """Test transition_integration raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to transition integration"), + ): + with pytest.raises(APIError) as exc_info: + transition_integration( + chronicle_client, + integration_name="test-integration", + target_mode=TargetMode.STAGING, + ) + + assert "Failed to transition integration" in str(exc_info.value) + + +# -- update_integration tests -- + + +def test_update_integration_uses_build_patch_body_and_passes_dependencies_to_remove( + chronicle_client, +): + """Test update_integration uses build_patch_body and adds dependenciesToRemove.""" + body = {"displayName": "New"} + params = {"updateMask": "displayName"} + + with patch( + "secops.chronicle.soar.integration.integrations.build_patch_body", + return_value=(body, params), + ) as mock_build_patch, patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value={"name": "integrations/test"}, + ) as mock_request: + result = update_integration( + chronicle_client, + integration_name="test-integration", + display_name="New", + dependencies_to_remove=["dep1", "dep2"], + update_mask="displayName", + ) + + assert result == {"name": "integrations/test"} + + mock_build_patch.assert_called_once() + mock_request.assert_called_once_with( + chronicle_client, + method="PATCH", + endpoint_path="integrations/test-integration", + json=body, + params={"updateMask": "displayName", "dependenciesToRemove": "dep1,dep2"}, + api_version=APIVersion.V1BETA, + ) + + +def test_update_integration_when_build_patch_body_returns_no_params(chronicle_client): + """Test update_integration handles params=None from build_patch_body.""" + body = {"description": "New"} + + with patch( + "secops.chronicle.soar.integration.integrations.build_patch_body", + return_value=(body, None), + ), patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value={"name": "integrations/test"}, + ) as mock_request: + update_integration( + chronicle_client, + integration_name="test-integration", + description="New", + dependencies_to_remove=["dep1"], + ) + + mock_request.assert_called_once_with( + chronicle_client, + method="PATCH", + endpoint_path="integrations/test-integration", + json=body, + params={"dependenciesToRemove": "dep1"}, + api_version=APIVersion.V1BETA, + ) + + +def test_update_integration_error(chronicle_client): + """Test update_integration raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.build_patch_body", + return_value=({}, None), + ), patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to update integration"), + ): + with pytest.raises(APIError) as exc_info: + update_integration(chronicle_client, "test-integration") + + assert "Failed to update integration" in str(exc_info.value) + + +# -- update_custom_integration tests -- + + +def test_update_custom_integration_builds_body_and_params(chronicle_client): + """Test update_custom_integration builds nested integration body and updateMask param.""" + expected = {"successful": True, "integration": {"name": "integrations/test"}} + + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = update_custom_integration( + chronicle_client, + integration_name="test-integration", + display_name="New", + staging=False, + dependencies_to_remove=["dep1"], + update_mask="displayName,staging", + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="integrations/test-integration:updateCustomIntegration", + json={ + "integration": { + "name": "test-integration", + "displayName": "New", + "staging": False, + }, + "dependenciesToRemove": ["dep1"], + }, + params={"updateMask": "displayName,staging"}, + api_version=APIVersion.V1BETA, + ) + + +def test_update_custom_integration_excludes_none_fields(chronicle_client): + """Test update_custom_integration excludes None fields from integration object.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + return_value={"successful": True}, + ) as mock_request: + update_custom_integration( + chronicle_client, + integration_name="test-integration", + display_name=None, + description=None, + image_base64=None, + svg_icon=None, + python_version=None, + parameters=None, + categories=None, + integration_type=None, + staging=None, + dependencies_to_remove=None, + update_mask=None, + ) + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="integrations/test-integration:updateCustomIntegration", + json={"integration": {"name": "test-integration"}}, + params=None, + api_version=APIVersion.V1BETA, + ) + + +def test_update_custom_integration_error(chronicle_client): + """Test update_custom_integration raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.integrations.chronicle_request", + side_effect=APIError("Failed to update custom integration"), + ): + with pytest.raises(APIError) as exc_info: + update_custom_integration(chronicle_client, "test-integration") + + assert "Failed to update custom integration" in str(exc_info.value) diff --git a/tests/chronicle/soar/integration/test_marketplace_integrations.py b/tests/chronicle/soar/integration/test_marketplace_integrations.py new file mode 100644 index 00000000..0c508c20 --- /dev/null +++ b/tests/chronicle/soar/integration/test_marketplace_integrations.py @@ -0,0 +1,522 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Tests for Chronicle marketplace integration functions.""" + +from unittest.mock import Mock, patch + +import pytest + +from secops.chronicle.client import ChronicleClient +from secops.chronicle.models import APIVersion +from secops.chronicle.soar.integration.marketplace_integrations import ( + list_marketplace_integrations, + get_marketplace_integration, + get_marketplace_integration_diff, + install_marketplace_integration, + uninstall_marketplace_integration, +) +from secops.exceptions import APIError + + +@pytest.fixture +def chronicle_client(): + """Create a Chronicle client for testing.""" + with patch("secops.auth.SecOpsAuth") as mock_auth: + mock_session = Mock() + mock_session.headers = {} + mock_auth.return_value.session = mock_session + return ChronicleClient( + customer_id="test-customer", + project_id="test-project", + default_api_version=APIVersion.V1BETA, + ) + + +@pytest.fixture +def mock_response() -> Mock: + """Create a mock API response object.""" + mock = Mock() + mock.status_code = 200 + mock.json.return_value = {} + return mock + + +@pytest.fixture +def mock_error_response() -> Mock: + """Create a mock error API response object.""" + mock = Mock() + mock.status_code = 400 + mock.text = "Error message" + mock.raise_for_status.side_effect = Exception("API Error") + return mock + + +# -- list_marketplace_integrations tests -- + + +def test_list_marketplace_integrations_success(chronicle_client): + """Test list_marketplace_integrations delegates to chronicle_paginated_request.""" + expected = { + "marketplaceIntegrations": [ + {"name": "integration1"}, + {"name": "integration2"}, + ] + } + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated: + result = list_marketplace_integrations( + chronicle_client, + page_size=10, + page_token="next-token", + ) + + assert result == expected + + mock_paginated.assert_called_once_with( + chronicle_client, + api_version=APIVersion.V1BETA, + path="marketplaceIntegrations", + items_key="marketplaceIntegrations", + page_size=10, + page_token="next-token", + extra_params={}, + as_list=False, + ) + + +def test_list_marketplace_integrations_default_args(chronicle_client): + """Test list_marketplace_integrations with default args.""" + expected = {"marketplaceIntegrations": []} + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated: + result = list_marketplace_integrations(chronicle_client) + + assert result == expected + + mock_paginated.assert_called_once_with( + chronicle_client, + api_version=APIVersion.V1BETA, + path="marketplaceIntegrations", + items_key="marketplaceIntegrations", + page_size=None, + page_token=None, + extra_params={}, + as_list=False, + ) + + +def test_list_marketplace_integrations_with_filter(chronicle_client): + """Test list_marketplace_integrations passes filter_string in extra_params.""" + expected = {"marketplaceIntegrations": [{"name": "integration1"}]} + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated: + result = list_marketplace_integrations( + chronicle_client, + filter_string='displayName = "My Integration"', + ) + + assert result == expected + + mock_paginated.assert_called_once_with( + chronicle_client, + api_version=APIVersion.V1BETA, + path="marketplaceIntegrations", + items_key="marketplaceIntegrations", + page_size=None, + page_token=None, + extra_params={"filter": 'displayName = "My Integration"'}, + as_list=False, + ) + + +def test_list_marketplace_integrations_with_order_by(chronicle_client): + """Test list_marketplace_integrations passes order_by in extra_params.""" + expected = {"marketplaceIntegrations": []} + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated: + result = list_marketplace_integrations( + chronicle_client, + order_by="displayName", + ) + + assert result == expected + + mock_paginated.assert_called_once_with( + chronicle_client, + api_version=APIVersion.V1BETA, + path="marketplaceIntegrations", + items_key="marketplaceIntegrations", + page_size=None, + page_token=None, + extra_params={"orderBy": "displayName"}, + as_list=False, + ) + + +def test_list_marketplace_integrations_with_filter_and_order_by(chronicle_client): + """Test list_marketplace_integrations with both filter_string and order_by.""" + expected = {"marketplaceIntegrations": []} + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated: + result = list_marketplace_integrations( + chronicle_client, + filter_string='displayName = "My Integration"', + order_by="displayName", + ) + + assert result == expected + + mock_paginated.assert_called_once_with( + chronicle_client, + api_version=APIVersion.V1BETA, + path="marketplaceIntegrations", + items_key="marketplaceIntegrations", + page_size=None, + page_token=None, + extra_params={ + "filter": 'displayName = "My Integration"', + "orderBy": "displayName", + }, + as_list=False, + ) + + +def test_list_marketplace_integrations_as_list(chronicle_client): + """Test list_marketplace_integrations with as_list=True.""" + expected = [{"name": "integration1"}, {"name": "integration2"}] + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_paginated_request", + return_value=expected, + ) as mock_paginated: + result = list_marketplace_integrations(chronicle_client, as_list=True) + + assert result == expected + + mock_paginated.assert_called_once_with( + chronicle_client, + api_version=APIVersion.V1BETA, + path="marketplaceIntegrations", + items_key="marketplaceIntegrations", + page_size=None, + page_token=None, + extra_params={}, + as_list=True, + ) + + +def test_list_marketplace_integrations_error(chronicle_client): + """Test list_marketplace_integrations propagates APIError from helper.""" + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_paginated_request", + side_effect=APIError("Failed to list marketplace integrations"), + ): + with pytest.raises(APIError) as exc_info: + list_marketplace_integrations(chronicle_client) + + assert "Failed to list marketplace integrations" in str(exc_info.value) + + +# -- get_marketplace_integration tests -- + + +def test_get_marketplace_integration_success(chronicle_client): + """Test get_marketplace_integration returns expected result.""" + expected = { + "name": "test-integration", + "displayName": "Test Integration", + "version": "1.0.0", + } + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_marketplace_integration(chronicle_client, "test-integration") + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path="marketplaceIntegrations/test-integration", + api_version=APIVersion.V1BETA, + ) + + +def test_get_marketplace_integration_error(chronicle_client): + """Test get_marketplace_integration raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + side_effect=APIError("Failed to get marketplace integration test-integration"), + ): + with pytest.raises(APIError) as exc_info: + get_marketplace_integration(chronicle_client, "test-integration") + + assert "Failed to get marketplace integration" in str(exc_info.value) + + +# -- get_marketplace_integration_diff tests -- + + +def test_get_marketplace_integration_diff_success(chronicle_client): + """Test get_marketplace_integration_diff returns expected result.""" + expected = { + "name": "test-integration", + "diff": {"added": [], "removed": [], "modified": []}, + } + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = get_marketplace_integration_diff(chronicle_client, "test-integration") + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="GET", + endpoint_path=( + "marketplaceIntegrations/test-integration" + ":fetchCommercialDiff" + ), + api_version=APIVersion.V1BETA, + ) + + +def test_get_marketplace_integration_diff_error(chronicle_client): + """Test get_marketplace_integration_diff raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + side_effect=APIError("Failed to get marketplace integration diff"), + ): + with pytest.raises(APIError) as exc_info: + get_marketplace_integration_diff(chronicle_client, "test-integration") + + assert "Failed to get marketplace integration diff" in str(exc_info.value) + + +# -- install_marketplace_integration tests -- + + +def test_install_marketplace_integration_no_optional_fields(chronicle_client): + """Test install_marketplace_integration with no optional fields sends empty body.""" + expected = { + "name": "test-integration", + "displayName": "Test Integration", + "installedVersion": "1.0.0", + } + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = install_marketplace_integration( + chronicle_client, + integration_name="test-integration", + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="marketplaceIntegrations/test-integration:install", + json={}, + api_version=APIVersion.V1BETA, + ) + + +def test_install_marketplace_integration_all_fields(chronicle_client): + """Test install_marketplace_integration with all optional fields.""" + expected = { + "name": "test-integration", + "displayName": "Test Integration", + "installedVersion": "2.0.0", + } + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = install_marketplace_integration( + chronicle_client, + integration_name="test-integration", + override_mapping=True, + staging=False, + version="2.0.0", + restore_from_snapshot=True, + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="marketplaceIntegrations/test-integration:install", + json={ + "overrideMapping": True, + "staging": False, + "version": "2.0.0", + "restoreFromSnapshot": True, + }, + api_version=APIVersion.V1BETA, + ) + + +def test_install_marketplace_integration_override_mapping_only(chronicle_client): + """Test install_marketplace_integration with only override_mapping set.""" + expected = {"name": "test-integration"} + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = install_marketplace_integration( + chronicle_client, + integration_name="test-integration", + override_mapping=True, + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="marketplaceIntegrations/test-integration:install", + json={"overrideMapping": True}, + api_version=APIVersion.V1BETA, + ) + + +def test_install_marketplace_integration_version_only(chronicle_client): + """Test install_marketplace_integration with only version set.""" + expected = {"name": "test-integration"} + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = install_marketplace_integration( + chronicle_client, + integration_name="test-integration", + version="1.2.3", + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="marketplaceIntegrations/test-integration:install", + json={"version": "1.2.3"}, + api_version=APIVersion.V1BETA, + ) + + +def test_install_marketplace_integration_none_fields_excluded(chronicle_client): + """Test that None optional fields are not included in the request body.""" + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + return_value={"name": "test-integration"}, + ) as mock_request: + install_marketplace_integration( + chronicle_client, + integration_name="test-integration", + override_mapping=None, + staging=None, + version=None, + restore_from_snapshot=None, + ) + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="marketplaceIntegrations/test-integration:install", + json={}, + api_version=APIVersion.V1BETA, + ) + + +def test_install_marketplace_integration_error(chronicle_client): + """Test install_marketplace_integration raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + side_effect=APIError("Failed to install marketplace integration"), + ): + with pytest.raises(APIError) as exc_info: + install_marketplace_integration( + chronicle_client, + integration_name="test-integration", + ) + + assert "Failed to install marketplace integration" in str(exc_info.value) + + +# -- uninstall_marketplace_integration tests -- + + +def test_uninstall_marketplace_integration_success(chronicle_client): + """Test uninstall_marketplace_integration returns expected result.""" + expected = {} + + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + return_value=expected, + ) as mock_request: + result = uninstall_marketplace_integration( + chronicle_client, + integration_name="test-integration", + ) + + assert result == expected + + mock_request.assert_called_once_with( + chronicle_client, + method="POST", + endpoint_path="marketplaceIntegrations/test-integration:uninstall", + api_version=APIVersion.V1BETA, + ) + + +def test_uninstall_marketplace_integration_error(chronicle_client): + """Test uninstall_marketplace_integration raises APIError on failure.""" + with patch( + "secops.chronicle.soar.integration.marketplace_integrations.chronicle_request", + side_effect=APIError("Failed to uninstall marketplace integration"), + ): + with pytest.raises(APIError) as exc_info: + uninstall_marketplace_integration( + chronicle_client, + integration_name="test-integration", + ) + + assert "Failed to uninstall marketplace integration" in str(exc_info.value) \ No newline at end of file diff --git a/tests/chronicle/utils/test_format_utils.py b/tests/chronicle/utils/test_format_utils.py index c71bda40..5610c2da 100644 --- a/tests/chronicle/utils/test_format_utils.py +++ b/tests/chronicle/utils/test_format_utils.py @@ -18,6 +18,7 @@ import pytest from secops.chronicle.utils.format_utils import ( + build_patch_body, format_resource_id, parse_json_list, ) @@ -98,3 +99,107 @@ def test_parse_json_list_handles_empty_json_array() -> None: def test_parse_json_list_handles_empty_list_input() -> None: result = parse_json_list([], "filters") assert result == [] + + +def test_build_patch_body_all_fields_set_builds_body_and_mask() -> None: + # All three fields provided — body and mask should include all of them + body, params = build_patch_body([ + ("displayName", "display_name", "My Rule"), + ("enabled", "enabled", True), + ("severity", "severity", "HIGH"), + ]) + + assert body == {"displayName": "My Rule", "enabled": True, "severity": "HIGH"} + assert params == {"updateMask": "display_name,enabled,severity"} + + +def test_build_patch_body_partial_fields_omits_none_values() -> None: + # Only non-None values should appear in body and mask + body, params = build_patch_body([ + ("displayName", "display_name", "New Name"), + ("enabled", "enabled", None), + ("severity", "severity", None), + ]) + + assert body == {"displayName": "New Name"} + assert params == {"updateMask": "display_name"} + + +def test_build_patch_body_no_fields_set_returns_empty_body_and_none_params() -> None: + # All values are None — body should be empty and params should be None + body, params = build_patch_body([ + ("displayName", "display_name", None), + ("enabled", "enabled", None), + ]) + + assert body == {} + assert params is None + + +def test_build_patch_body_empty_field_map_returns_empty_body_and_none_params() -> None: + # Empty field_map — nothing to build + body, params = build_patch_body([]) + + assert body == {} + assert params is None + + +def test_build_patch_body_explicit_update_mask_overrides_auto_generated() -> None: + # An explicit update_mask should always win, even when fields are set + body, params = build_patch_body( + [ + ("displayName", "display_name", "Name"), + ("enabled", "enabled", True), + ], + update_mask="display_name", + ) + + assert body == {"displayName": "Name", "enabled": True} + assert params == {"updateMask": "display_name"} + + +def test_build_patch_body_explicit_update_mask_with_no_fields_set_still_applies() -> None: + # Explicit mask should appear even when all values are None (caller's intent) + body, params = build_patch_body( + [ + ("displayName", "display_name", None), + ], + update_mask="display_name", + ) + + assert body == {} + assert params == {"updateMask": "display_name"} + + +def test_build_patch_body_false_and_zero_are_not_treated_as_none() -> None: + # False-like but non-None values (False, 0, "") should be included in the body + body, params = build_patch_body([ + ("enabled", "enabled", False), + ("count", "count", 0), + ("label", "label", ""), + ]) + + assert body == {"enabled": False, "count": 0, "label": ""} + assert params == {"updateMask": "enabled,count,label"} + + +def test_build_patch_body_single_field_produces_single_entry_mask() -> None: + body, params = build_patch_body([ + ("severity", "severity", "LOW"), + ]) + + assert body == {"severity": "LOW"} + assert params == {"updateMask": "severity"} + + +def test_build_patch_body_mask_order_matches_field_map_order() -> None: + # The mask field order should mirror the order of field_map entries + body, params = build_patch_body([ + ("z", "z_key", "z_val"), + ("a", "a_key", "a_val"), + ("m", "m_key", "m_val"), + ]) + + assert params == {"updateMask": "z_key,a_key,m_key"} + assert list(body.keys()) == ["z", "a", "m"] + diff --git a/tests/chronicle/utils/test_request_utils.py b/tests/chronicle/utils/test_request_utils.py index 6f8687a2..c4e8b5b9 100644 --- a/tests/chronicle/utils/test_request_utils.py +++ b/tests/chronicle/utils/test_request_utils.py @@ -26,6 +26,7 @@ from secops.chronicle.utils.request_utils import ( DEFAULT_PAGE_SIZE, chronicle_request, + chronicle_request_bytes, chronicle_paginated_request, ) from secops.exceptions import APIError @@ -655,3 +656,181 @@ def test_chronicle_request_non_json_error_body_is_truncated(client: Mock) -> Non assert "status=500" in msg # Should not include the full 5000 chars, should include truncation marker assert "truncated" in msg + + +# --------------------------------------------------------------------------- +# chronicle_request_bytes() tests +# --------------------------------------------------------------------------- + +def test_chronicle_request_bytes_success_returns_content_and_stream_true(client: Mock) -> None: + resp = _mock_response(status_code=200, json_value={"ignored": True}) + resp.content = b"PK\x03\x04...zip-bytes..." # ZIP magic prefix in real life + client.session.request.return_value = resp + + out = chronicle_request_bytes( + client=client, + method="GET", + endpoint_path="integrations/foo:export", + api_version=APIVersion.V1BETA, + params={"alt": "media"}, + headers={"Accept": "application/zip"}, + ) + + assert out == b"PK\x03\x04...zip-bytes..." + + client.base_url.assert_called_once_with(APIVersion.V1BETA) + client.session.request.assert_called_once_with( + method="GET", + url="https://example.test/chronicle/instances/instance-1/integrations/foo:export", + params={"alt": "media"}, + headers={"Accept": "application/zip"}, + timeout=None, + stream=True, + ) + + +def test_chronicle_request_bytes_builds_url_for_rpc_colon_prefix(client: Mock) -> None: + resp = _mock_response(status_code=200, json_value={"ok": True}) + resp.content = b"binary" + client.session.request.return_value = resp + + out = chronicle_request_bytes( + client=client, + method="POST", + endpoint_path=":exportSomething", + api_version=APIVersion.V1ALPHA, + ) + + assert out == b"binary" + + _, kwargs = client.session.request.call_args + assert kwargs["url"] == "https://example.test/chronicle/instances/instance-1:exportSomething" + assert kwargs["stream"] is True + + +def test_chronicle_request_bytes_accepts_multiple_expected_statuses_set(client: Mock) -> None: + resp = _mock_response(status_code=204, json_value=None) + resp.content = b"" + client.session.request.return_value = resp + + out = chronicle_request_bytes( + client=client, + method="DELETE", + endpoint_path="something", + api_version=APIVersion.V1ALPHA, + expected_status={200, 204}, + ) + + assert out == b"" + + +def test_chronicle_request_bytes_status_mismatch_with_json_includes_json(client: Mock) -> None: + resp = _mock_response(status_code=400, json_value={"error": "bad"}) + resp.content = b"" + client.session.request.return_value = resp + + with pytest.raises( + APIError, + match=r"API request failed: method=GET, " + r"url=https://example\.test/chronicle/instances/instance-1/curatedRules" + r", status=400, response={'error': 'bad'}", + ): + chronicle_request_bytes( + client=client, + method="GET", + endpoint_path="curatedRules", + api_version=APIVersion.V1ALPHA, + ) + + +def test_chronicle_request_bytes_status_mismatch_non_json_includes_text(client: Mock) -> None: + resp = _mock_response(status_code=500, json_raises=True, text="boom") + resp.content = b"" + client.session.request.return_value = resp + + with pytest.raises( + APIError, + match=r"API request failed: method=GET, " + r"url=https://example\.test/chronicle/instances/instance-1/curatedRules, " + r"status=500, response_text=boom", + ): + chronicle_request_bytes( + client=client, + method="GET", + endpoint_path="curatedRules", + api_version=APIVersion.V1ALPHA, + ) + + +def test_chronicle_request_bytes_custom_error_message_used(client: Mock) -> None: + resp = _mock_response(status_code=404, json_value={"message": "not found"}) + resp.content = b"" + client.session.request.return_value = resp + + with pytest.raises( + APIError, + match=r"Failed to download export: method=GET, " + r"url=https://example\.test/chronicle/instances/instance-1/integrations/foo:export, " + r"status=404, response={'message': 'not found'}", + ): + chronicle_request_bytes( + client=client, + method="GET", + endpoint_path="integrations/foo:export", + api_version=APIVersion.V1BETA, + error_message="Failed to download export", + ) + + +def test_chronicle_request_bytes_wraps_requests_exception(client: Mock) -> None: + client.session.request.side_effect = requests.RequestException("no route to host") + + with pytest.raises(APIError) as exc_info: + chronicle_request_bytes( + client=client, + method="GET", + endpoint_path="curatedRules", + api_version=APIVersion.V1ALPHA, + ) + + msg = str(exc_info.value) + assert "API request failed" in msg + assert "method=GET" in msg + assert "url=https://example.test/chronicle/instances/instance-1/curatedRules" in msg + assert "request_error=RequestException" in msg + + +def test_chronicle_request_bytes_wraps_google_auth_error(client: Mock) -> None: + client.session.request.side_effect = GoogleAuthError("invalid_grant") + + with pytest.raises(APIError) as exc_info: + chronicle_request_bytes( + client=client, + method="GET", + endpoint_path="curatedRules", + api_version=APIVersion.V1ALPHA, + ) + + msg = str(exc_info.value) + assert "Google authentication failed" in msg + assert "authentication_error=" in msg + + +def test_chronicle_request_bytes_non_json_error_body_is_truncated(client: Mock) -> None: + long_text = "x" * 5000 + resp = _mock_response(status_code=500, json_raises=True, text=long_text) + resp.content = b"" + resp.headers = {"Content-Type": "text/plain"} + client.session.request.return_value = resp + + with pytest.raises(APIError) as exc_info: + chronicle_request_bytes( + client=client, + method="GET", + endpoint_path="curatedRules", + api_version=APIVersion.V1ALPHA, + ) + + msg = str(exc_info.value) + assert "status=500" in msg + assert "truncated" in msg \ No newline at end of file