Skip to content
Open
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
27 changes: 27 additions & 0 deletions CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,33 @@ secops reference-list update \
--entries-file "/path/to/updated_domains.txt"
```

### Featured Content Rules

Featured content rules are pre-built detection rules available in the Chronicle Content Hub. These curated rules can be listed and filtered to help you discover and deploy detections.

#### List all featured content rules:

```bash
secops featured-content-rules list
```

#### List with pagination:

```bash
# Get first page with 10 rules
secops featured-content-rules list --page-size 10

# Get next page using token from previous response
secops featured-content-rules list --page-size 10 --page-token "token123"
```

#### Get filtered list:

```bash
secops featured-content-rules list \
--filter 'category_name:"Threat Detection" AND rule_precision:"Precise"'
```

## Examples

### Search for Recent Network Connections
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2286,6 +2286,40 @@ activity = chronicle.compute_rule_exclusion_activity(
)
```

### 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.

```python
# List all featured content rules
rules = chronicle.list_featured_content_rules()
for rule in rules.get("featuredContentRules", []):
rule_id = rule.get("name", "").split("/")[-1]
content_metadata = rule.get("contentMetadata", {})
display_name = content_metadata.get("displayName", "Unknown")
severity = rule.get("severity", "UNSPECIFIED")
print(f"Rule: {display_name} [{rule_id}] - Severity: {severity}")

# List with pagination
result = chronicle.list_featured_content_rules(page_size=10)
rules = result.get("featuredContentRules", [])
next_page_token = result.get("nextPageToken")

if next_page_token:
next_page = chronicle.list_featured_content_rules(
page_size=10,
page_token=next_page_token
)

# Filter list
filtered_rules = chronicle.list_featured_content_rules(
filter_expression=(
'category_name:"Threat Detection" AND '
'rule_precision:"Precise"'
)
)
```

## 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.
Expand Down
1 change: 1 addition & 0 deletions api_module_mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Following shows mapping between SecOps [REST Resource](https://cloud.google.com/
| 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 |
Expand Down
106 changes: 106 additions & 0 deletions examples/featured_content_rules_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
# 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.
#
"""Example script demonstrating Chronicle Featured Content Rules."""

import argparse

from secops.chronicle.client import ChronicleClient


def get_client(project_id: str, customer_id: str, region: str):
"""Initialize Chronicle client."""
return ChronicleClient(
project_id=project_id, customer_id=customer_id, region=region
)


def featured_content_rules_list_example(chronicle):
"""Demonstrate featured content rules functionality."""
try:
print("\n[1] List All Featured Content Rules")
print("-" * 70)
result = chronicle.list_featured_content_rules()
rules = result.get("featuredContentRules", [])
print(f"Total rules found: {len(rules)}")

if rules:
print("\nFirst 3 rules:")
for i, rule in enumerate(rules[:3], 1):
name = rule.get("name", "")
rule_id_extracted = name.split("/")[-1] if name else "N/A"
content_metadata = rule.get("contentMetadata", {})
display_name = content_metadata.get("displayName", "Unknown")
severity = rule.get("severity", "UNSPECIFIED")
print(
f" {i}. {display_name} "
f"[{rule_id_extracted}] - {severity}"
)

print("\n[2] Paginated List (5 rules per page)")
print("-" * 70)
result = chronicle.list_featured_content_rules(page_size=5)
featured_rules = result.get("featuredContentRules", [])
next_token = result.get("nextPageToken")
print(f"Rules in first page: {len(featured_rules)}")
print(f"More pages available: {bool(next_token)}")

print("\n[3] Filter expression")
print("-" * 70)
filter_expr = 'rule_precision:"Precise"'
combined_rules_result = chronicle.list_featured_content_rules(
filter_expression=filter_expr
)
combined_rules = combined_rules_result.get("featuredContentRules", [])
print(f"Rules matching filter expression: {len(combined_rules)}")
except Exception as e:
print(f"\nError: {e}")


def main():
"""Main function to demonstrate featured content rules usage."""
parser = argparse.ArgumentParser(
description="Chronicle Featured Content Rules Example"
)
parser.add_argument(
"--project_id",
required=True,
help="Google Cloud Project ID",
)
parser.add_argument(
"--customer_id",
required=True,
help="Chronicle Customer ID (UUID)",
)
parser.add_argument(
"--region",
default="us",
help="Chronicle region (default: us)",
)

args = parser.parse_args()

chronicle = get_client(args.project_id, args.customer_id, args.region)

print(
f"\nConnected to Chronicle (Project: {args.project_id}, "
f"Customer: {args.customer_id}, Region: {args.region})\n"
)

featured_content_rules_list_example(chronicle)


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions src/secops/chronicle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@
search_curated_detections,
update_curated_rule_set_deployment,
)
from secops.chronicle.featured_content_rules import (
list_featured_content_rules,
)
from secops.chronicle.rule_validation import ValidationResult
from secops.chronicle.search import search_udm
from secops.chronicle.stats import get_stats
Expand Down Expand Up @@ -274,6 +277,8 @@
"get_curated_rule_by_name",
"update_curated_rule_set_deployment",
"search_curated_detections",
# Featured content rules operations
"list_featured_content_rules",
# Native Dashboard
"add_chart",
"create_dashboard",
Expand Down
37 changes: 37 additions & 0 deletions src/secops/chronicle/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@
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.stats import get_stats as _get_stats
Expand Down Expand Up @@ -2543,6 +2546,40 @@ def get_curated_rule_set(self, rule_set_id: str) -> dict[str, Any]:
"""
return _get_curated_rule_set(self, rule_set_id)

def list_featured_content_rules(
self,
page_size: int | None = None,
page_token: str | None = None,
filter_expression: str | None = None,
) -> list[dict[str, Any]] | dict[str, Any]:
"""List featured content rules from Chronicle Content Hub.
Args:
page_size: Maximum number of featured content rules to return.
If unspecified, at most 100 rules will be returned.
Maximum value is 1000. If provided, returns dict with
nextPageToken.
page_token: Token for retrieving the next page of results.
filter_expression: Optional filter expression. Supported:
- category_name:"<category_name>" (OR for multiple)
- policy_name:"<policy_name>" (OR for multiple)
- rule_id:"ur_<id>" (OR for multiple)
- rule_precision:"<rule_precision>" (Precise or Broad)
- search_rule_name_or_description=~"<text>"
Multiple filters can be combined with AND operator.
Returns:
If page_size is None: List of all featured content rules.
If page_size is provided: Dict with featuredContentRules
list and nextPageToken.
Raises:
APIError: If the API request fails
"""
return _list_featured_content_rules(
self, page_size, page_token, filter_expression
)

def search_curated_detections(
self,
rule_id: str,
Expand Down
67 changes: 67 additions & 0 deletions src/secops/chronicle/featured_content_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 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.
#
"""Featured content rules functionality for Chronicle."""

from typing import Any

from secops.chronicle.utils.request_utils import (
chronicle_paginated_request,
)


def list_featured_content_rules(
client,
page_size: int | None = None,
page_token: str | None = None,
filter_expression: str | None = None,
) -> list[dict[str, Any]] | dict[str, Any]:
"""List featured content rules from Chronicle Content Hub.
Args:
client: ChronicleClient instance
page_size: Maximum number of featured content rules to return.
If unspecified, at most 100 rules will be returned.
Maximum value is 1000; values above 1000 will be coerced
to 1000. If provided, returns dict with nextPageToken.
page_token: Token for retrieving the next page of results.
filter_expression: Optional filter expression. Supported filters:
- category_name:"<category_name>" (OR operator for multiple)
- policy_name:"<policy_name>" (OR operator for multiple)
- rule_id:"ur_<id>" (OR operator for multiple)
- rule_precision:"<rule_precision>" (Precise or Broad)
- search_rule_name_or_description=~"<text>"
Multiple filters can be combined with AND operator.
Returns:
If page_size is None: List of all featured content rules.
If page_size is provided: Dict with featuredContentRules list
and nextPageToken.
Raises:
APIError: If the API request fails
"""
extra_params = {}
if filter_expression:
extra_params["filter"] = filter_expression

return chronicle_paginated_request(
client,
base_url=client.base_url,
path="contentHub/featuredContentRules",
items_key="featuredContentRules",
page_size=page_size,
page_token=page_token,
extra_params=extra_params if extra_params else None,
)
4 changes: 4 additions & 0 deletions src/secops/cli/cli_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
from secops.cli.commands.config import setup_config_command
from secops.cli.commands.curated_rule import setup_curated_rules_command
from secops.cli.commands.dashboard import setup_dashboard_command
from secops.cli.commands.featured_content_rules import (
setup_featured_content_rules_command,
)
from secops.cli.commands.dashboard_query import setup_dashboard_query_command
from secops.cli.commands.data_table import setup_data_table_command
from secops.cli.commands.entity import setup_entity_command
Expand Down Expand Up @@ -176,6 +179,7 @@ def build_parser() -> argparse.ArgumentParser:
setup_rule_exclusion_command(subparsers)
setup_forwarder_command(subparsers)
setup_curated_rules_command(subparsers)
setup_featured_content_rules_command(subparsers)
setup_config_command(subparsers)
setup_help_command(subparsers)
setup_dashboard_command(subparsers)
Expand Down
Loading