feat(bitdefender): expand GravityZone control surface + correct policy docs

Re-verified the live tenant's full API scope and wrapped the modules the key
allows but the skill didn't expose. New CLI subcommands:
- assign-policy (gated) — apply an existing policy to endpoints/groups
  (param shape policyId+targetIds verified live)
- reports, accounts, notif-settings, scan-tasks — read
- push-settings / push-stats / push-set (gated) — push event service
  (status param verified; needs a receiver URL to enable)

Corrections from live probing:
- policies are NOT shallow: getPolicyDetails returns the FULL granular config.
  Removed the false "shallow" warning; documented read+assign, console-only authoring.
- raw now gates assignPolicy + setPushEventSettings.
- documented dead modules (patchmanagement/phasr/maintenancewindows/integrations,
  incidents.getIncidentsList) and unconfigured-push handled cleanly (rc0, no errorlog).

selftest 29/29 -> 42/42, all green against the live tenant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 10:02:25 -07:00
parent 1f65facb6f
commit d622a05b84
5 changed files with 417 additions and 41 deletions

View File

@@ -168,6 +168,31 @@ def _print_incidents_table(data: dict) -> None:
f"{i.get('severity', i.get('status',''))}")
def _print_reports_table(data: dict) -> None:
items = data.get("items", [])
print(f"Reports: {data.get('total', len(items))}")
for r in items:
print(f" {str(r.get('id','?')):26} {str(r.get('name','')):40} "
f"type={r.get('type','')}")
def _print_accounts_table(data: dict) -> None:
items = data.get("items", [])
print(f"Accounts: {data.get('total', len(items))}")
for a in items:
prof = a.get("profile", {}) or {}
print(f" {str(a.get('id','?')):26} {str(a.get('email','')):34} "
f"{prof.get('fullName','')}")
def _print_scan_tasks_table(data: dict) -> None:
items = data.get("items", [])
print(f"Scan tasks: {data.get('total', len(items))}")
for t in items:
print(f" {str(t.get('id','?')):26} {str(t.get('name','')):30} "
f"status={t.get('status','')}")
def _print_inventory_table(cache: dict) -> None:
print(f"Inventory cached_at: {cache.get('fetched_at')}")
print(f" companies: {len(cache.get('companies', {}))}")
@@ -220,11 +245,90 @@ def cmd_policies(client, args):
def cmd_policy(client, args):
print("[WARNING] Public API returns shallow policy detail only "
"(no granular config).", file=sys.stderr)
# getPolicyDetails returns the FULL granular module configuration (verified
# live 2026-06-21). Use --json for the complete settings tree; the table
# view shows the top-level keys only.
_emit(client.get_policy_details(args.policy_id), args.json, _print_kv)
def cmd_reports(client, args):
_emit(client.list_reports(page=args.page, per_page=args.per_page),
args.json, _print_reports_table)
def cmd_accounts(client, args):
_emit(client.list_accounts(page=args.page, per_page=args.per_page),
args.json, _print_accounts_table)
def cmd_notif_settings(client, args):
_emit(client.get_notifications_settings(), args.json, _print_kv)
def cmd_scan_tasks(client, args):
_emit(client.list_scan_tasks(page=args.page, per_page=args.per_page),
args.json, _print_scan_tasks_table)
def cmd_assign_policy(client, args):
desc = (f"assign policy {args.policy} to {len(args.targets)} target(s): "
f"{','.join(args.targets)}")
if not _gated(desc, args.confirm):
return 3
result = client.assign_policy(
args.policy, args.targets, force_inheritance=args.force_inheritance
)
_emit({"assignedPolicy": args.policy, "targets": args.targets,
"result": result}, args.json, _print_kv)
return 0
def _push_read(emit_fn) -> int:
"""Run a push read, treating 'never configured' as an expected (non-error)
state rather than a failure (so it does not pollute errorlog)."""
try:
emit_fn()
return 0
except GravityZoneError as exc:
msg = str(exc).lower()
if "not set" in msg or "are not" in msg or "not available" in msg:
print("[INFO] Push event service is not configured on this tenant.")
return 0
raise
def cmd_push_settings(client, args):
return _push_read(
lambda: _emit(client.get_push_settings(), args.json, _print_kv)
)
def cmd_push_stats(client, args):
return _push_read(
lambda: _emit(client.get_push_stats(), args.json, _print_kv)
)
def cmd_push_set(client, args):
state = "ENABLE" if args.status == 1 else "DISABLE"
if args.status == 1 and not args.url:
print("[ERROR] --url is required to enable the push event service.",
file=sys.stderr)
return 2
desc = f"{state} GravityZone push event service (url={args.url or '-'})"
if not _gated(desc, args.confirm):
return 3
result = client.set_push_settings(
status=args.status,
service_type=args.service_type,
url=args.url,
require_valid_ssl=not args.allow_insecure_ssl,
authorization=args.authorization,
)
_emit({"pushService": state, "result": result}, args.json, _print_kv)
return 0
def cmd_packages(client, args):
_emit(client.list_packages(), args.json, _print_package_table)
@@ -288,7 +392,8 @@ def cmd_make_group(client, args):
# (EDR) module — gate them in `raw` as well as via the dedicated subcommands.
DESTRUCTIVE_RAW_PATTERNS = ("delete", "createuninstall", "createremove",
"createreconfigure", "isolat", "addtoblocklist",
"removefromblocklist")
"removefromblocklist", "assignpolicy",
"setpushevent")
def _is_destructive_method(method: str) -> bool:
@@ -419,12 +524,34 @@ def build_parser() -> argparse.ArgumentParser:
sp.add_argument("--company", help="Parent id (defaults to ACG container).")
sub.add_parser("policies", help="List policies (id + name).", parents=[common])
sp = sub.add_parser("policy", help="Shallow detail for one policy.",
sp = sub.add_parser("policy",
help="Full granular config for one policy (use --json).",
parents=[common])
sp.add_argument("policy_id")
sub.add_parser("packages", help="List installation packages.", parents=[common])
sp = sub.add_parser("reports", help="List saved reports.", parents=[common])
sp.add_argument("--page", type=int, default=1)
sp.add_argument("--per-page", type=int, default=100)
sp = sub.add_parser("accounts", help="List GravityZone console accounts.",
parents=[common])
sp.add_argument("--page", type=int, default=1)
sp.add_argument("--per-page", type=int, default=100)
sub.add_parser("notif-settings", help="Show notification settings.",
parents=[common])
sp = sub.add_parser("scan-tasks", help="List scan tasks.", parents=[common])
sp.add_argument("--page", type=int, default=1)
sp.add_argument("--per-page", type=int, default=100)
sub.add_parser("push-settings",
help="Show push event service settings.", parents=[common])
sub.add_parser("push-stats",
help="Show push event service delivery stats.", parents=[common])
sp = sub.add_parser("quarantine", help="List quarantine items for a company.",
parents=[common])
sp.add_argument("--company", required=True)
@@ -533,6 +660,32 @@ def build_parser() -> argparse.ArgumentParser:
help="hashItemId — the 'id' from `blocklist` output.")
sp.add_argument("--confirm", action="store_true")
sp = sub.add_parser("assign-policy",
help="Assign an existing policy to endpoints/groups (gated).",
parents=[common])
sp.add_argument("--policy", required=True, help="policyId to assign.")
sp.add_argument("--targets", nargs="+", required=True,
help="One or more endpoint/group ids.")
sp.add_argument("--force-inheritance", action="store_true",
help="Force policy inheritance to sub-items.")
sp.add_argument("--confirm", action="store_true")
sp = sub.add_parser("push-set",
help="Configure the push event service (gated).",
parents=[common])
sp.add_argument("--status", type=int, required=True, choices=[0, 1],
help="1=enable, 0=disable.")
sp.add_argument("--url",
help="Receiver URL GravityZone POSTs events to "
"(required to enable).")
sp.add_argument("--service-type", default="jsonRPC",
help="jsonRPC|splunk|cef (default jsonRPC).")
sp.add_argument("--authorization",
help="Optional Authorization header the receiver expects.")
sp.add_argument("--allow-insecure-ssl", action="store_true",
help="Do not require a valid SSL cert on the receiver.")
sp.add_argument("--confirm", action="store_true")
return p
@@ -545,6 +698,14 @@ HANDLERS = {
"policies": cmd_policies,
"policy": cmd_policy,
"packages": cmd_packages,
"reports": cmd_reports,
"accounts": cmd_accounts,
"notif-settings": cmd_notif_settings,
"scan-tasks": cmd_scan_tasks,
"push-settings": cmd_push_settings,
"push-stats": cmd_push_stats,
"assign-policy": cmd_assign_policy,
"push-set": cmd_push_set,
"quarantine": cmd_quarantine,
"blocklist": cmd_blocklist,
"incidents": cmd_incidents,