fix(bitdefender): correct assignPolicy + isolate/unisolate param shapes (live-verified)
Found during the RMM-TEST-MACHINE full-function test (live tenant): - assignPolicy: assigning a policyId REQUIRES inheritFromAbove:false in the same call, else the API rejects with a misleading "inheritFromAbove should not be used with policyId" error. Fixed assign_policy to always send it; dropped the wrong --inherit-from-above flag. - isolate/unisolate: the API takes a SINGLE endpointId per call, NOT an endpointIds array (errored "not expected"). Client now loops per endpoint. unisolate fails while the isolate task is in progress — wait + retry. - api-reference updated with the live-verified shapes. Full function test PASSED on RMM-TEST-MACHINE: install(offline kit/SYSTEM) -> enroll -> move(ZZ-RMM-TEST) -> assign-policy(GPS Base, applied) -> set-label -> scan -> reconfigure -> isolate -> unisolate -> quarantine/blocklist read -> managed uninstall(deleteEndpoint). selftest 75/75. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -140,7 +140,7 @@ In `getNetworkInventoryItems` results, `type == 1` denotes a company node.
|
||||
|---|---|---|---|
|
||||
| `getPoliciesList` | `page?, perPage?` | VERIFIED | List policies (id, name). |
|
||||
| `getPolicyDetails` | `policyId` | VERIFIED | **Full** granular config (not shallow). |
|
||||
| `assignPolicy` (`/network`) | `policyId, targetIds[], forcePolicyInheritance?, inheritFromAbove?` | VERIFIED (official docs + probe) | Assign existing policy to endpoints/containers. NOT applied to targets with an ENFORCED policy. CLI `assign-policy`, gated. STATE-CHANGING. Docs: 77212-924802-assignpolicy.html |
|
||||
| `assignPolicy` (`/network`) | `policyId, targetIds[], inheritFromAbove:false (REQUIRED w/ policyId), forcePolicyInheritance?` | VERIFIED LIVE 2026-06-21 | Assigning a policyId REQUIRES `inheritFromAbove:false` in the same call or the API rejects with a misleading "inheritFromAbove should not be used" error. `{targetIds, inheritFromAbove:true}` (no policyId) = make target inherit. CLI `assign-policy`, gated. |
|
||||
|
||||
## reports (`/reports`) — COMPLETE
|
||||
|
||||
@@ -204,8 +204,8 @@ In `getNetworkInventoryItems` results, `type == 1` denotes a company node.
|
||||
|---|---|---|---|
|
||||
| `getBlocklistItems` | `companyId?, page?, perPage?` | VERIFIED LIVE | Returns `{total, page, perPage, pagesCount, items:[{id, source, sourceInfo, hashType, hash, companyId}]}`. Returned 26 items live. `perPage` defaults to 100 in the CLI. `companyId` scopes to one company; omit for the whole tenant. |
|
||||
| `getIncidentsList` | `parentId, page?, perPage (500-10000), filters?` | UNVERIFIED / possibly unavailable | `parentId` = a company/group id and is REQUIRED. `perPage` must be 500-10000 (the API rejected 100 with "Invalid value for 'perPage' parameter. The value should be between 500 and 10000"); the CLI defaults it to 500. **However**, live re-testing on 2026-05-30 returned `Method not found` for this method on the `/incidents` module, while `getBlocklistItems` on the SAME module succeeds in the same request — so this is NOT rate-limiting or a bad key. The method is likely gated behind an EDR/incidents license feature that is OFF on this tenant, or is named differently in this API version. The CLI `incidents` subcommand is wired up but will surface `Method not found` until the feature is enabled / the correct name is confirmed. |
|
||||
| `createIsolateEndpointTask` | `endpointIds[]` | VERIFIED (destructive) | v1.2: takes an ARRAY `endpointIds` (max 1000), returns an array of task ids. Cuts the endpoint off the network. CLI-gated behind `--confirm`; the client enforces the 1000-id cap. |
|
||||
| `createRestoreEndpointFromIsolationTask` | `endpointIds[]` | VERIFIED (destructive) | v1.2: takes an ARRAY `endpointIds` (max 1000), returns an array of task ids. Un-isolates (reverses `createIsolateEndpointTask`). CLI-gated behind `--confirm`; the client enforces the 1000-id cap. |
|
||||
| `createIsolateEndpointTask` | `endpointId` (SINGLE) | VERIFIED LIVE 2026-06-21 | Takes ONE `endpointId` per call (NOT an `endpointIds` array — that errors "not expected"). Client loops for multiple. Cuts endpoint off network (BD mgmt link preserved). CLI `isolate`, gated. |
|
||||
| `createRestoreEndpointFromIsolationTask` | `endpointId` (SINGLE) | VERIFIED LIVE 2026-06-21 | One `endpointId` per call. FAILS if the isolation task is still in progress ("cannot be restored") — wait + retry. CLI `unisolate`, gated. |
|
||||
| `addToBlocklist` | `companyId, hashType, hashList[], sourceInfo, operatingSystems?` | VERIFIED (destructive) | `hashType` is an int (1 is the common value seen live; see the console / API docs for the full mapping). `hashList` is an array of hash strings. `sourceInfo` is a free-text description. CLI-gated behind `--confirm`. |
|
||||
| `removeFromBlocklist` | `hashItemId` *(UNVERIFIED param name)* | VERIFIED method, UNVERIFIED param | Removes one blocklist entry. The param name `hashItemId` is UNVERIFIED — the `id` field from `getBlocklistItems` is the candidate. Confirm against the official API reference before relying on it. CLI-gated behind `--confirm`; the CLI `--id` value comes from `blocklist` output. |
|
||||
| `getCustomRulesList` | `page?, perPage?` | VERIFIED LIVE | EDR custom rules (returned 1 live). CLI `custom-rules`. |
|
||||
|
||||
@@ -420,7 +420,6 @@ def cmd_assign_policy(client, args):
|
||||
result = client.assign_policy(
|
||||
args.policy, args.targets,
|
||||
force_inheritance=args.force_inheritance,
|
||||
inherit_from_above=args.inherit_from_above,
|
||||
)
|
||||
_emit({"assignedPolicy": args.policy, "targets": args.targets,
|
||||
"result": result}, args.json, _print_kv)
|
||||
@@ -1032,8 +1031,6 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
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("--inherit-from-above", action="store_true",
|
||||
help="Inherit the policy from the parent group.")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("account-create", help="Create a console account (gated).",
|
||||
|
||||
@@ -612,14 +612,14 @@ class GravityZoneClient:
|
||||
v1.2 takes an ARRAY `endpointIds` (max 1000) and returns an array of
|
||||
task ids. STATE-CHANGING — gate behind --confirm at the call site.
|
||||
"""
|
||||
if len(endpoint_ids) > 1000:
|
||||
raise GravityZoneError(
|
||||
"isolate_endpoints accepts at most 1000 endpoint ids per call "
|
||||
f"(got {len(endpoint_ids)})."
|
||||
)
|
||||
return self._jsonrpc_request(
|
||||
"incidents", "createIsolateEndpointTask", {"endpointIds": endpoint_ids}
|
||||
)
|
||||
# VERIFIED LIVE 2026-06-21: the API takes a SINGLE `endpointId` per call
|
||||
# (NOT an `endpointIds` array — that errors "not expected"). Loop for many.
|
||||
results = []
|
||||
for eid in endpoint_ids:
|
||||
results.append(self._jsonrpc_request(
|
||||
"incidents", "createIsolateEndpointTask", {"endpointId": eid}
|
||||
))
|
||||
return results if len(results) != 1 else results[0]
|
||||
|
||||
def restore_endpoints_from_isolation(self, endpoint_ids: list[str]) -> Any:
|
||||
"""Un-isolate endpoints (incidents.createRestoreEndpointFromIsolationTask).
|
||||
@@ -627,16 +627,16 @@ class GravityZoneClient:
|
||||
v1.2 takes an ARRAY `endpointIds` (max 1000) and returns an array of
|
||||
task ids. STATE-CHANGING — gate behind --confirm at the call site.
|
||||
"""
|
||||
if len(endpoint_ids) > 1000:
|
||||
raise GravityZoneError(
|
||||
"restore_endpoints_from_isolation accepts at most 1000 endpoint "
|
||||
f"ids per call (got {len(endpoint_ids)})."
|
||||
)
|
||||
return self._jsonrpc_request(
|
||||
"incidents",
|
||||
"createRestoreEndpointFromIsolationTask",
|
||||
{"endpointIds": endpoint_ids},
|
||||
)
|
||||
# VERIFIED LIVE 2026-06-21: single `endpointId` per call (not an array).
|
||||
# Note: fails if the isolation task is still in progress — wait + retry.
|
||||
results = []
|
||||
for eid in endpoint_ids:
|
||||
results.append(self._jsonrpc_request(
|
||||
"incidents",
|
||||
"createRestoreEndpointFromIsolationTask",
|
||||
{"endpointId": eid},
|
||||
))
|
||||
return results if len(results) != 1 else results[0]
|
||||
|
||||
def add_to_blocklist(
|
||||
self,
|
||||
@@ -689,22 +689,26 @@ class GravityZoneClient:
|
||||
policy_id: str,
|
||||
target_ids: list[str],
|
||||
force_inheritance: bool = False,
|
||||
inherit_from_above: bool = False,
|
||||
) -> Any:
|
||||
"""Assign an existing policy to endpoints/groups (network.assignPolicy).
|
||||
|
||||
Param shape VERIFIED against the official docs + live validation probe
|
||||
(2026-06-21): `policyId` and `targetIds` (endpoint/container ids) are
|
||||
required; `forcePolicyInheritance` and `inheritFromAbove` are optional
|
||||
bools. NOTE: the policy is NOT applied to targets that already carry an
|
||||
ENFORCED policy. STATE-CHANGING — gate at the call site behind --confirm.
|
||||
VERIFIED LIVE 2026-06-21: assigning a `policyId` REQUIRES sending
|
||||
`inheritFromAbove=false` in the same call. An inheriting target defaults
|
||||
inheritFromAbove to true, so omitting it makes the API reject the call
|
||||
with a misleading "inheritFromAbove should not be used with policyId"
|
||||
error. `forcePolicyInheritance` optionally pushes the policy down to
|
||||
sub-items. (To make a target INHERIT instead, call with
|
||||
{targetIds, inheritFromAbove:true} and no policyId via `raw`.)
|
||||
STATE-CHANGING — gate at the call site behind --confirm.
|
||||
Docs: bitdefender.com/business/support/en/77212-924802-assignpolicy.html
|
||||
"""
|
||||
params: dict = {"policyId": policy_id, "targetIds": target_ids}
|
||||
params: dict = {
|
||||
"policyId": policy_id,
|
||||
"targetIds": target_ids,
|
||||
"inheritFromAbove": False,
|
||||
}
|
||||
if force_inheritance:
|
||||
params["forcePolicyInheritance"] = True
|
||||
if inherit_from_above:
|
||||
params["inheritFromAbove"] = True
|
||||
return self._jsonrpc_request("network", "assignPolicy", params)
|
||||
|
||||
def list_scan_tasks(
|
||||
|
||||
Reference in New Issue
Block a user