Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.27.0] - 2025-12-05
### Added
- Chronicle configuration for default API endpoint version to use for all methods
### Updated
- Following module's methods to add support for configuring API endpoint version:
- Feed management (list, get, create, delete, disable, enable, generate secret)
- Reference list management(create, get, list, update)
- Rule management (create, get, list, update, delete, search)
- Rule deployment (get, update)
- Rule retrohunt (create, get)

## [0.26.0] - 2025-11-26
### Added
- Search curated rule detection method support
Expand Down
19 changes: 19 additions & 0 deletions CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ You can also save your service account path:
secops config set --service-account "/path/to/service-account.json" --customer-id "your-instance-id" --project-id "your-project-id" --region "us"
```

Set the default API version for Chronicle API calls:

```bash
secops config set --api-version "v1"
```

**Supported API versions:**
- `v1` - Stable production API (recommended)
- `v1beta` - Beta API with newer features
- `v1alpha` - Alpha API with experimental features (default)

Additionally, you can set default time parameters:

```bash
Expand Down Expand Up @@ -92,11 +103,19 @@ These parameters can be used with most commands:
- `--customer-id ID` - Chronicle instance ID
- `--project-id ID` - GCP project ID
- `--region REGION` - Chronicle API region (default: us)
- `--api-version VERSION` - Chronicle API version (v1, v1beta, v1alpha; default: v1alpha)
- `--output FORMAT` - Output format (json, text)
- `--start-time TIME` - Start time in ISO format (YYYY-MM-DDTHH:MM:SSZ)
- `--end-time TIME` - End time in ISO format (YYYY-MM-DDTHH:MM:SSZ)
- `--time-window HOURS` - Time window in hours (alternative to start/end time)

You can override the configured API version on a per-command basis:

```bash
# Use v1 for a specific command, even if config has v1alpha
secops rule list --api-version v1
```

## Commands

### Search UDM Events
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,37 @@ chronicle = client.chronicle(
```
[See available regions](https://github.com/google/secops-wrapper/blob/main/regions.md)

#### API Version Control

The SDK supports flexible API version selection:

- **Default Version**: Set `default_api_version` during client initialization (default is `v1alpha`)
- **Per-Method Override**: Many methods accept an `api_version` parameter to override the default for specific calls

**Supported API versions:**
- `v1` - Stable production API
- `v1beta` - Beta API with newer features
- `v1alpha` - Alpha API with experimental features

**Example with per-method version override:**
```python
from secops.chronicle.models import APIVersion

# Client defaults to v1alpha
chronicle = client.chronicle(
customer_id="your-chronicle-instance-id",
project_id="your-project-id",
region="us",
default_api_version="v1alpha"
)

# Use v1 for a specific rule operation
rule = chronicle.get_rule(
rule_id="ru_12345678-1234-1234-1234-123456789abc",
api_version=APIVersion.V1 # Override to use v1 for this call
)
```

### Log Ingestion

Ingest raw logs directly into Chronicle:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "secops"
version = "0.26.0"
version = "0.27.0"
description = "Python SDK for wrapping the Google SecOps API for common use cases"
readme = "README.md"
requires-python = ">=3.7"
Expand Down
93 changes: 69 additions & 24 deletions src/secops/chronicle/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from secops.auth import RetryConfig
from secops.chronicle.alert import get_alerts as _get_alerts
from secops.chronicle.case import get_cases_from_list
from secops.chronicle.models import APIVersion
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
Expand Down Expand Up @@ -274,6 +275,67 @@ class ValueType(Enum):
USERNAME = "USERNAME"


class BaseUrl(str):
"""Chronicle base url generation based on version and region.

Supports production, dev, and staging regions with appropriate
domain names.
"""

def __new__(cls, version: APIVersion, region: str = "us"):
domain = cls._get_domain(region)
return super().__new__(cls, f"https://{domain}/{version}")

def __init__(self, version: APIVersion, region: str = "us"):
self._default = version
self._region = region

@staticmethod
def _get_domain(region: str) -> str:
"""Get the appropriate domain for the given region.

Args:
region: Region identifier (e.g., 'us', 'europe', 'dev',
'staging')

Returns:
str: Domain name for the region
"""
if region == "dev":
return "autopush-chronicle.sandbox.googleapis.com"
elif region == "staging":
return "staging-chronicle.sandbox.googleapis.com"
else:
return f"{region}-chronicle.googleapis.com"

def __call__(
self, version: APIVersion = None, allowed: list[APIVersion] = None
) -> str:
"""
Returns the base URL for a specific API version.

Args:
version: (Optional) The API version to use. If not provided,
uses the default.
allowed: (Optional) A list of allowed API versions for the
endpoint.

Returns:
The base URL string.

Raises:
SecOpsError: If the requested API version is not supported.
"""
selected_version = APIVersion(version or self._default)
if allowed and selected_version not in allowed:
raise SecOpsError(
f"API version '{selected_version}' is not supported for this "
f"endpoint. Allowed versions: {', '.join(allowed)}"
)
domain = self._get_domain(self._region)
return f"https://{domain}/{selected_version}"


def _detect_value_type(value: str) -> tuple[Optional[str], Optional[str]]:
"""Detect value type from a string.

Expand Down Expand Up @@ -337,6 +399,7 @@ def __init__(
extra_scopes: Optional[List[str]] = None,
credentials: Optional[Any] = None,
retry_config: Optional[Union[RetryConfig, Dict[str, Any], bool]] = None,
default_api_version: Union[APIVersion, str] = APIVersion.V1ALPHA,
):
"""Initialize ChronicleClient.

Expand All @@ -350,48 +413,30 @@ def __init__(
credentials: Credentials object
retry_config: Request retry configurations.
If set to false, retry will be disabled.
default_api_version: Default API version to use for requests.
"""
self.project_id = project_id
self.customer_id = customer_id
self.region = region
self.default_api_version = APIVersion(default_api_version)
self._default_forwarder_display_name: str = "Wrapper-SDK-Forwarder"
self._cached_default_forwarder_id: Optional[str] = None

# Format the instance ID to match the expected format
if region in ["dev", "staging"]:
# For dev and staging environments,
# use a different instance ID format
# Dev and staging use 'us' as the location
self.instance_id = (
f"projects/{project_id}/locations/us/instances/{customer_id}"
)
# Set up the base URL for dev/staging
if region == "dev":
self.base_url = (
"https://autopush-chronicle.sandbox.googleapis.com/v1alpha"
)
self.base_v1_url = (
"https://autopush-chronicle.sandbox.googleapis.com/v1"
)
else: # staging
self.base_url = (
"https://staging-chronicle.sandbox.googleapis.com/v1alpha"
)
self.base_v1_url = (
"https://staging-chronicle.sandbox.googleapis.com/v1"
)
else:
# Standard production regions use the normal format
self.instance_id = (
f"projects/{project_id}/locations/{region}/"
f"instances/{customer_id}"
)
# Set up the base URL
self.base_url = (
f"https://{self.region}-chronicle.googleapis.com/v1alpha"
)
self.base_v1_url = (
f"https://{self.region}-chronicle.googleapis.com/v1"
)

# Set up base URLs using BaseUrl for all regions
self.base_url = BaseUrl(self.default_api_version, self.region)

# Create a session with authentication
if session:
Expand Down
Loading
Loading