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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ 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.28.1] - 2025-12-11
### Updated
- CLI to show help when required sub-command/argument not provided.

## [0.28.0] - 2025-12-10
### Updated
- Minimum python version support to 3.10 from 3.9 as python 3.9 has reached its end of life.
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.28.0"
version = "0.28.1"
description = "Python SDK for wrapping the Google SecOps API for common use cases"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
5 changes: 2 additions & 3 deletions src/secops/cli/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ def setup_config_command(subparsers):
config_parser = subparsers.add_parser(
"config", help="Manage CLI configuration"
)
config_subparsers = config_parser.add_subparsers(
help="Config command", required=True
)
config_subparsers = config_parser.add_subparsers(help="Config command")
config_parser.set_defaults(func=lambda args: config_parser.print_help())

# Set config command
set_parser = config_subparsers.add_parser(
Expand Down
19 changes: 12 additions & 7 deletions src/secops/cli/commands/curated_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ def setup_curated_rules_command(subparsers):
top = subparsers.add_parser(
"curated-rule", help="Manage curated rules and rule sets"
)
lvl1 = top.add_subparsers(dest="curated_cmd", required=True)
lvl1 = top.add_subparsers(dest="curated_cmd")
top.set_defaults(func=lambda args, _: top.print_help())

# ---- rules ----
rules = lvl1.add_parser("rule", help="Manage curated rules")
rules_sp = rules.add_subparsers(dest="rule_cmd", required=True)
rules_sp = rules.add_subparsers(dest="rule_cmd")
rules.set_defaults(func=lambda args, _: rules.print_help())

rules_list = rules_sp.add_parser("list", help="List curated rules")
add_pagination_args(rules_list)
Expand Down Expand Up @@ -96,7 +98,8 @@ def setup_curated_rules_command(subparsers):

# ---- rule-set ----
rule_set = lvl1.add_parser("rule-set", help="Manage curated rule sets")
rule_set_subparser = rule_set.add_subparsers(dest="rset_cmd", required=True)
rule_set_subparser = rule_set.add_subparsers(dest="rset_cmd")
rule_set.set_defaults(func=lambda args, _: rule_set.print_help())

rule_set_list = rule_set_subparser.add_parser(
"list", help="List curated rule sets"
Expand All @@ -116,9 +119,8 @@ def setup_curated_rules_command(subparsers):
rule_set_cat = lvl1.add_parser(
"rule-set-category", help="Manage curated rule set categories"
)
rule_set_cat_subparser = rule_set_cat.add_subparsers(
dest="rcat_cmd", required=True
)
rule_set_cat_subparser = rule_set_cat.add_subparsers(dest="rcat_cmd")
rule_set_cat.set_defaults(func=lambda args, _: rule_set_cat.print_help())

rule_set_cat_list = rule_set_cat_subparser.add_parser(
"list", help="List curated rule set categories"
Expand All @@ -141,7 +143,10 @@ def setup_curated_rules_command(subparsers):
"rule-set-deployment", help="Manage curated rule set deployments"
)
rule_set_deployment_subparser = rule_set_deployment.add_subparsers(
dest="rdep_cmd", required=True
dest="rdep_cmd"
)
rule_set_deployment.set_defaults(
func=lambda args, _: rule_set_deployment.print_help()
)

rule_set_deployment_list = rule_set_deployment_subparser.add_parser(
Expand Down
4 changes: 3 additions & 1 deletion src/secops/cli/commands/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ def setup_dashboard_command(subparsers):
dashboard_subparsers = dashboard_parser.add_subparsers(
dest="dashboard_command",
help="Dashboard command to execute",
required=True,
)
dashboard_parser.set_defaults(
func=lambda args, _: dashboard_parser.print_help()
)

# List dashboards
Expand Down
4 changes: 3 additions & 1 deletion src/secops/cli/commands/dashboard_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ def setup_dashboard_query_command(subparsers):
dashboard_query_subparsers = dashboard_query_parser.add_subparsers(
dest="dashboard_query_command",
help="Dashboard query command to execute",
required=True,
)
dashboard_query_parser.set_defaults(
func=lambda args, _: dashboard_query_parser.print_help()
)

# Execute query
Expand Down
1 change: 1 addition & 0 deletions src/secops/cli/commands/data_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def setup_data_table_command(subparsers):
dt_subparsers = dt_parser.add_subparsers(
dest="dt_command", help="Data table command"
)
dt_parser.set_defaults(func=lambda args, _: dt_parser.print_help())

# List data tables command
list_parser = dt_subparsers.add_parser("list", help="List data tables")
Expand Down
1 change: 1 addition & 0 deletions src/secops/cli/commands/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def setup_export_command(subparsers):
export_subparsers = export_parser.add_subparsers(
dest="export_command", help="Export command"
)
export_parser.set_defaults(func=lambda args, _: export_parser.print_help())

# List available log types command
log_types_parser = export_subparsers.add_parser(
Expand Down
1 change: 1 addition & 0 deletions src/secops/cli/commands/feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def setup_feed_command(subparsers):
feed_subparsers = feed_parser.add_subparsers(
dest="feed_command", help="Feed command"
)
feed_parser.set_defaults(func=lambda args, _: feed_parser.print_help())

# List feeds command
list_parser = feed_subparsers.add_parser("list", help="List feeds")
Expand Down
3 changes: 3 additions & 0 deletions src/secops/cli/commands/forwarder.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ def setup_forwarder_command(subparsers):
forwarder_subparsers = forwarder_parser.add_subparsers(
dest="forwarder_command", help="Forwarder command"
)
forwarder_parser.set_defaults(
func=lambda args, _: forwarder_parser.print_help()
)

# Create forwarder command
create_parser = forwarder_subparsers.add_parser(
Expand Down
5 changes: 2 additions & 3 deletions src/secops/cli/commands/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@
def setup_log_command(subparsers):
"""Set up the log command parser."""
log_parser = subparsers.add_parser("log", help="Ingest logs")
log_subparsers = log_parser.add_subparsers(
help="Log command", required=True
)
log_subparsers = log_parser.add_subparsers(help="Log command")
log_parser.set_defaults(func=lambda args, _: log_parser.print_help())

# Ingest log command
ingest_parser = log_subparsers.add_parser("ingest", help="Ingest raw logs")
Expand Down
1 change: 1 addition & 0 deletions src/secops/cli/commands/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def setup_parser_command(subparsers):
parser_subparsers = parser_parser.add_subparsers(
dest="parser_command", help="Parser command"
)
parser_parser.set_defaults(func=lambda args, _: parser_parser.print_help())

# --- Activate Parser Command ---
activate_parser_sub = parser_subparsers.add_parser(
Expand Down
3 changes: 2 additions & 1 deletion src/secops/cli/commands/parser_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def setup_parser_extension_command(subparsers: Any) -> None:
"parser-extension",
help="Manage parser extensions",
)
parser_ext_sub = parser_ext.add_subparsers(dest="subcommand", required=True)
parser_ext_sub = parser_ext.add_subparsers(dest="subcommand")
parser_ext.set_defaults(func=lambda args, _: parser_ext.print_help())

# Create parser extension
create = parser_ext_sub.add_parser(
Expand Down
1 change: 1 addition & 0 deletions src/secops/cli/commands/reference_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def setup_reference_list_command(subparsers):
rl_subparsers = rl_parser.add_subparsers(
dest="rl_command", help="Reference list command"
)
rl_parser.set_defaults(func=lambda args, _: rl_parser.print_help())

# List reference lists command
list_parser = rl_subparsers.add_parser("list", help="List reference lists")
Expand Down
1 change: 1 addition & 0 deletions src/secops/cli/commands/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def setup_rule_command(subparsers):
rule_subparsers = rule_parser.add_subparsers(
dest="rule_command", help="Rule command"
)
rule_parser.set_defaults(func=lambda args, _: rule_parser.print_help())

# List rules command
list_parser = rule_subparsers.add_parser("list", help="List rules")
Expand Down
1 change: 1 addition & 0 deletions src/secops/cli/commands/rule_exclusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def setup_rule_exclusion_command(subparsers):
re_subparsers = re_parser.add_subparsers(
dest="re_command", help="Rule exclusion command"
)
re_parser.set_defaults(func=lambda args, _: re_parser.print_help())

# Create rule exclusion command
create_parser = re_subparsers.add_parser(
Expand Down
17 changes: 14 additions & 3 deletions src/secops/cli/commands/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@ def setup_search_command(subparsers):
subparsers: Subparsers object to add to
"""
search_parser = subparsers.add_parser("search", help="Search UDM events")
search_parser.add_argument("--query", help="UDM query string")
search_parser.add_argument(

# Create mutually exclusive group for query types
query_group = search_parser.add_mutually_exclusive_group()
query_group.add_argument("--query", help="UDM query string")
query_group.add_argument(
"--nl-query",
"--nl_query",
dest="nl_query",
help="Natural language query",
)

search_parser.add_argument(
"--max-events",
"--max_events",
Expand Down Expand Up @@ -78,10 +82,17 @@ def handle_search_command(args, chronicle):
args: Command line arguments
chronicle: Chronicle client
"""
# Require query or nl_query
if not args.query and not args.nl_query:
print(
"\nError: One of --query or --nl-query is required", file=sys.stderr
)
sys.exit(1)

start_time, end_time = get_time_range(args)

try:
if args.csv and args.fields:
if args.csv and args.fields and args.query:
fields = [f.strip() for f in args.fields.split(",")]
result = chronicle.fetch_udm_search_csv(
query=args.query,
Expand Down
Loading