feat(sentinelone): add 13 commands (threat analysis, UAM alerts, remote-script status/results, PowerQuery, tag rule, fetch-file, endpoint logs)
Brings the SentinelOne integration to 83 commands. New: threat-analysis, threat-download-from-cloud, abort-endpoint-scan, endpoint-fetch-logs, fetch-file, get-remote-script-task-status, get-remote-script-task-results, get-service-users, list-installed-singularity-marketplace-applications, update-uam-alert-status, update-uam-alert-verdict, run-powerquery (Singularity Data Lake), create-tag-rule. Each command ships a stdlib-only script against API v2.1.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
id: sentinelone
|
||||
name: SentinelOne
|
||||
version: 1.1.0
|
||||
version: 1.2.0
|
||||
description: "SentinelOne Singularity (API v2.1) — endpoint detection & response: triage threats, enrich, isolate/reconnect hosts, mitigate, scan."
|
||||
changelog: "1.1.0 — Command names prefixed with 'sentinelone-' (e.g. sentinelone-isolate-agent) for easier toolbox search; command IDs unchanged. 1.0.0 — Initial release: 70 commands covering agents, threats, alerts, blocklist/exclusions, IOCs, STAR rules, Deep Visibility, remote scripts, tags, firewall and network discovery based on the SentinelOne API v2.1."
|
||||
changelog: "1.2.0 — Added 13 commands: threat-analysis, threat-download-from-cloud, abort-endpoint-scan, endpoint-fetch-logs, fetch-file, get-remote-script-task-status/results, get-service-users, list-installed-singularity-marketplace-applications, update-uam-alert-status/verdict, run-powerquery and create-tag-rule (83 commands total). 1.1.0 — Command names prefixed with 'sentinelone-' (e.g. sentinelone-isolate-agent) for easier toolbox search; command IDs unchanged. 1.0.0 — Initial release: 70 commands covering agents, threats, alerts, blocklist/exclusions, IOCs, STAR rules, Deep Visibility, remote scripts, tags, firewall and network discovery based on the SentinelOne API v2.1."
|
||||
category: endpoint
|
||||
|
||||
# Per-instance configuration. The scripts build the API base as <url>/web/api/v2.1.
|
||||
@@ -790,3 +790,168 @@ commands:
|
||||
limit: { type: number, description: "Max results (default 50)." }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Threat analysis / sample ──────────────────────────────────────────────
|
||||
- id: threat_analysis
|
||||
name: sentinelone-threat-analysis
|
||||
description: "Return the detailed analysis for a threat (agent, detection and threat info)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
threat_id: { type: string, description: "Threat ID." }
|
||||
required: [threat_id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: threat_download_from_cloud
|
||||
name: sentinelone-threat-download-from-cloud
|
||||
description: "Get a download URL for a threat's sample from the cloud (BinaryVault)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
threat_id: { type: string, description: "Threat ID." }
|
||||
required: [threat_id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Endpoint actions ──────────────────────────────────────────────────────
|
||||
- id: abort_endpoint_scan
|
||||
name: sentinelone-abort-endpoint-scan
|
||||
description: "Abort the disk scan on the given agents."
|
||||
inputs_schema:
|
||||
properties:
|
||||
agent_ids: { type: string, description: "Comma-separated agent IDs." }
|
||||
required: [agent_ids]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: endpoint_fetch_logs
|
||||
name: sentinelone-endpoint-fetch-logs
|
||||
description: "Fetch agent/endpoint logs from the given agents."
|
||||
inputs_schema:
|
||||
properties:
|
||||
agent_ids: { type: string, description: "Comma-separated agent IDs." }
|
||||
agents_logs: { type: boolean, description: "Fetch agent logs (default true)." }
|
||||
customer_facing_logs: { type: boolean, description: "Fetch customer-facing logs (default false)." }
|
||||
platform_logs: { type: boolean, description: "Fetch platform logs (default false)." }
|
||||
required: [agent_ids]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: fetch_file
|
||||
name: sentinelone-fetch-file
|
||||
description: "Request a file fetch from an agent endpoint (collected as a password-protected zip)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
agent_id: { type: string, description: "Agent ID." }
|
||||
file_path: { type: string, description: "Absolute file path on the endpoint." }
|
||||
password: { type: string, description: "Password to protect the resulting zip." }
|
||||
required: [agent_id, file_path, password]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Remote scripts (status / results) ─────────────────────────────────────
|
||||
- id: get_remote_script_task_status
|
||||
name: sentinelone-get-remote-script-task-status
|
||||
description: "Get the status of remote-script tasks for a parent task."
|
||||
inputs_schema:
|
||||
properties:
|
||||
parent_task_id: { type: string, description: "Parent task ID." }
|
||||
ids: { type: string, description: "Comma-separated task IDs." }
|
||||
status: { type: string, description: "Filter by status (e.g. created, completed)." }
|
||||
computer_name_contains: { type: string, description: "Filter by computer name substring." }
|
||||
site_ids: { type: string, description: "Comma-separated site IDs." }
|
||||
account_ids: { type: string, description: "Comma-separated account IDs." }
|
||||
limit: { type: number, description: "Max results (default 50)." }
|
||||
required: [parent_task_id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: get_remote_script_task_results
|
||||
name: sentinelone-get-remote-script-task-results
|
||||
description: "Get download links for the results of remote-script tasks."
|
||||
inputs_schema:
|
||||
properties:
|
||||
task_ids: { type: string, description: "Comma-separated task IDs." }
|
||||
computer_names: { type: string, description: "Comma-separated computer names." }
|
||||
required: [task_ids]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Users / marketplace apps ──────────────────────────────────────────────
|
||||
- id: get_service_users
|
||||
name: sentinelone-get-service-users
|
||||
description: "List service users (API token accounts)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
account_ids: { type: string, description: "Comma-separated account IDs." }
|
||||
role_ids: { type: string, description: "Comma-separated RBAC role IDs." }
|
||||
ids: { type: string, description: "Comma-separated service-user IDs." }
|
||||
site_ids: { type: string, description: "Comma-separated site IDs." }
|
||||
limit: { type: number, description: "Max results (default 100)." }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: list_installed_singularity_marketplace_applications
|
||||
name: sentinelone-list-installed-singularity-marketplace-applications
|
||||
description: "List installed Singularity Marketplace applications."
|
||||
inputs_schema:
|
||||
properties:
|
||||
account_ids: { type: string, description: "Comma-separated account IDs." }
|
||||
application_catalog_id: { type: string, description: "Filter by application catalog ID." }
|
||||
creator_contains: { type: string, description: "Filter by creator substring." }
|
||||
ids: { type: string, description: "Comma-separated application IDs." }
|
||||
name_contains: { type: string, description: "Filter by application name substring." }
|
||||
site_ids: { type: string, description: "Comma-separated site IDs." }
|
||||
limit: { type: number, description: "Max results (default 100)." }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Unified (UAM) alerts ──────────────────────────────────────────────────
|
||||
- id: update_uam_alert_status
|
||||
name: sentinelone-update-uam-alert-status
|
||||
description: "Update the status of a unified (UAM) alert."
|
||||
inputs_schema:
|
||||
properties:
|
||||
alert_id: { type: string, description: "UAM alert ID (UUID)." }
|
||||
status: { type: string, description: "NEW | IN_PROGRESS | RESOLVED" }
|
||||
required: [alert_id, status]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: update_uam_alert_verdict
|
||||
name: sentinelone-update-uam-alert-verdict
|
||||
description: "Update the analyst verdict of a unified (UAM) alert."
|
||||
inputs_schema:
|
||||
properties:
|
||||
alert_id: { type: string, description: "UAM alert ID (UUID)." }
|
||||
analyst_verdict: { type: string, description: "Verdict enum, e.g. TRUE_POSITIVE_MALWARE, FALSE_POSITIVE_BENIGN, UNDEFINED." }
|
||||
required: [alert_id, analyst_verdict]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── PowerQuery (Singularity Data Lake) ────────────────────────────────────
|
||||
- id: run_powerquery
|
||||
name: sentinelone-run-powerquery
|
||||
description: "Run a PowerQuery against the Singularity Data Lake (separate XDR URL + Log Read API key)."
|
||||
inputs_schema:
|
||||
properties:
|
||||
singularity_xdr_url: { type: string, description: "Singularity Data Lake XDR URL (https://...)." }
|
||||
singularity_xdr_api_key: { type: string, description: "Log Read Access API key." }
|
||||
query: { type: string, description: "PowerQuery syntax query." }
|
||||
start_time: { type: string, description: "Query start time." }
|
||||
end_time: { type: string, description: "Query end time." }
|
||||
priority: { type: string, description: "Execution priority (default low)." }
|
||||
recurring: { type: boolean, description: "Create a materialized view (default false)." }
|
||||
team_emails: { type: string, description: "Comma-separated account emails for cross-team search." }
|
||||
required: [singularity_xdr_url, singularity_xdr_api_key, query]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Dynamic asset tag rule (XDR) ──────────────────────────────────────────
|
||||
- id: create_tag_rule
|
||||
name: sentinelone-create-tag-rule
|
||||
description: "Create a dynamic asset tag rule (XDR) that tags assets matching a property filter."
|
||||
inputs_schema:
|
||||
properties:
|
||||
name: { type: string, description: "Rule name." }
|
||||
account_id: { type: string, description: "Account ID the rule belongs to." }
|
||||
tag_id: { type: string, description: "Existing tag ID to apply." }
|
||||
filter_values: { type: string, description: "Comma-separated values (e.g. SRV-,WEB-)." }
|
||||
filter_name: { type: string, description: "Asset property to filter on (default assetName)." }
|
||||
filter_operand: { type: string, description: "startsWith | contains | equals | endsWith (default startsWith)." }
|
||||
conditions_operand: { type: string, description: "and | or (default or)." }
|
||||
scope_type: { type: string, description: "account | site | global (default account)." }
|
||||
site_ids: { type: string, description: "Site ID(s); first is used when scope_type=site." }
|
||||
status: { type: string, description: "enabled | disabled (default enabled)." }
|
||||
description: { type: string, description: "Rule description." }
|
||||
required: [name, account_id, tag_id, filter_values]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def csv(v):
|
||||
return [x.strip() for x in str(v or "").split(",") if x.strip()]
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
body = {"filter": {"ids": csv(inputs.get("agent_ids"))}, "data": {}}
|
||||
print(json.dumps(request("POST", base + "/agents/actions/abort-scan", headers, body)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,58 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def csv(v):
|
||||
return [x.strip() for x in str(v or "").split(",") if x.strip()]
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
scope_type = inputs.get("scope_type") or "account"
|
||||
scope_ids = {"accounts": [inputs.get("account_id")]}
|
||||
sites = csv(inputs.get("site_ids"))
|
||||
if scope_type == "site" and sites:
|
||||
scope_ids["sites"] = [sites[0]]
|
||||
payload = {
|
||||
"name": inputs.get("name"),
|
||||
"description": inputs.get("description") or "",
|
||||
"status": inputs.get("status") or "enabled",
|
||||
"conditions": {
|
||||
"operand": inputs.get("conditions_operand") or "or",
|
||||
"properties": [
|
||||
{
|
||||
"name": inputs.get("filter_name") or "assetName",
|
||||
"operand": inputs.get("filter_operand") or "startsWith",
|
||||
"values": csv(inputs.get("filter_values")),
|
||||
}
|
||||
],
|
||||
},
|
||||
"tags": [{"id": inputs.get("tag_id")}],
|
||||
"scopes": {"scopeType": scope_type, "scopeIds": scope_ids},
|
||||
"excludedAssets": [],
|
||||
}
|
||||
print(json.dumps(request("POST", base + "/xdr/assets/tags/rules", headers, payload)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,49 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def csv(v):
|
||||
return [x.strip() for x in str(v or "").split(",") if x.strip()]
|
||||
|
||||
|
||||
def flag(v, default):
|
||||
if v is None:
|
||||
return default
|
||||
return str(v).lower() in ("true", "1", "yes")
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
body = {
|
||||
"filter": {"ids": csv(inputs.get("agent_ids"))},
|
||||
"data": {
|
||||
"agentLogs": flag(inputs.get("agents_logs"), True),
|
||||
"customerFacingLogs": flag(inputs.get("customer_facing_logs"), False),
|
||||
"platformLogs": flag(inputs.get("platform_logs"), False),
|
||||
},
|
||||
}
|
||||
print(json.dumps(request("POST", base + "/agents/actions/fetch-logs", headers, body)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,33 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
agent_id = urllib.parse.quote(str(inputs.get("agent_id", "")), safe="")
|
||||
body = {"data": {"password": inputs.get("password"), "files": [inputs.get("file_path")]}}
|
||||
print(json.dumps(request("POST", base + "/agents/" + agent_id + "/actions/fetch-files", headers, body)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,38 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def csv(v):
|
||||
return [x.strip() for x in str(v or "").split(",") if x.strip()]
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
data = {"taskIds": csv(inputs.get("task_ids"))}
|
||||
if inputs.get("computer_names"):
|
||||
data["computerNames"] = csv(inputs["computer_names"])
|
||||
print(json.dumps(request("POST", base + "/remote-scripts/fetch-files", headers, {"data": data})))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,41 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
qs = {
|
||||
"parentTaskId": inputs.get("parent_task_id"),
|
||||
"ids": inputs.get("ids"),
|
||||
"computerName__contains": inputs.get("computer_name_contains"),
|
||||
"status": inputs.get("status"),
|
||||
"siteIds": inputs.get("site_ids"),
|
||||
"accountIds": inputs.get("account_ids"),
|
||||
"limit": int(inputs.get("limit") or 50),
|
||||
}
|
||||
url = base + "/remote-scripts/status?" + urllib.parse.urlencode({k: v for k, v in qs.items() if v not in (None, "")})
|
||||
print(json.dumps(request("GET", url, headers)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,39 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
qs = {
|
||||
"accountIds": inputs.get("account_ids"),
|
||||
"roleIds": inputs.get("role_ids"),
|
||||
"ids": inputs.get("ids"),
|
||||
"siteIds": inputs.get("site_ids"),
|
||||
"limit": int(inputs.get("limit") or 100),
|
||||
}
|
||||
url = base + "/service-users?" + urllib.parse.urlencode({k: v for k, v in qs.items() if v not in (None, "")})
|
||||
print(json.dumps(request("GET", url, headers)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
qs = {
|
||||
"accountIds": inputs.get("account_ids"),
|
||||
"applicationCatalogId": inputs.get("application_catalog_id"),
|
||||
"creator__contains": inputs.get("creator_contains"),
|
||||
"ids": inputs.get("ids"),
|
||||
"name__contains": inputs.get("name_contains"),
|
||||
"siteIds": inputs.get("site_ids"),
|
||||
"limit": int(inputs.get("limit") or 100),
|
||||
}
|
||||
url = base + "/singularity-marketplace/applications?" + urllib.parse.urlencode({k: v for k, v in qs.items() if v not in (None, "")})
|
||||
print(json.dumps(request("GET", url, headers)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,52 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def csv(v):
|
||||
return [x.strip() for x in str(v or "").split(",") if x.strip()]
|
||||
|
||||
|
||||
def flag(v):
|
||||
return str(v).lower() in ("true", "1", "yes")
|
||||
|
||||
|
||||
def main():
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
# PowerQuery runs against the Singularity Data Lake, not the management console.
|
||||
sdl_url = str(inputs.get("singularity_xdr_url", "")).rstrip("/")
|
||||
if not sdl_url.startswith("https://"):
|
||||
raise ValueError("singularity_xdr_url must start with https://")
|
||||
headers = {
|
||||
"Authorization": "Bearer " + str(inputs.get("singularity_xdr_api_key", "")),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload = {"query": inputs.get("query")}
|
||||
if inputs.get("start_time"):
|
||||
payload["startTime"] = inputs["start_time"]
|
||||
if inputs.get("end_time"):
|
||||
payload["endTime"] = inputs["end_time"]
|
||||
if inputs.get("priority"):
|
||||
payload["priority"] = inputs["priority"]
|
||||
if inputs.get("team_emails"):
|
||||
payload["teamEmails"] = csv(inputs["team_emails"])
|
||||
if inputs.get("recurring") is not None:
|
||||
payload["recurring"] = flag(inputs["recurring"])
|
||||
print(json.dumps(request("POST", sdl_url + "/api/powerQuery", headers, payload)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,32 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
threat_id = urllib.parse.quote(str(inputs.get("threat_id", "")), safe="")
|
||||
print(json.dumps(request("GET", base + "/private/threats/" + threat_id + "/analysis", headers)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,32 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
threat_id = urllib.parse.quote(str(inputs.get("threat_id", "")), safe="")
|
||||
print(json.dumps(request("GET", base + "/threats/" + threat_id + "/download-from-cloud", headers)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,42 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
MUTATION = (
|
||||
"mutation AlertTriggerActions($id: String!, $status: Status!) {"
|
||||
" alertTriggerActions("
|
||||
" filter: { or: [ { and: [ { fieldId: \"id\", stringEqual: { value: $id } } ] } ] },"
|
||||
" actions: [ { id: \"S1/alert/statusUpdate\", payload: { status: { value: $status } } } ]"
|
||||
" ) { __typename ... on ActionsTriggered { actions { actionId alertCount"
|
||||
" success { id } failure { id errorType errorMessage } skip { id } } } } }"
|
||||
)
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
variables = {"id": inputs.get("alert_id"), "status": inputs.get("status")}
|
||||
body = {"query": MUTATION, "variables": variables}
|
||||
print(json.dumps(request("POST", base + "/unifiedalerts/graphql", headers, body)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,42 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
MUTATION = (
|
||||
"mutation AlertTriggerActions($id: String!, $verdict: AnalystVerdict!) {"
|
||||
" alertTriggerActions("
|
||||
" filter: { or: [ { and: [ { fieldId: \"id\", stringEqual: { value: $id } } ] } ] },"
|
||||
" actions: [ { id: \"S1/alert/analystVerdictUpdate\", payload: { analystVerdict: { value: $verdict } } } ]"
|
||||
" ) { __typename ... on ActionsTriggered { actions { actionId alertCount"
|
||||
" success { id } failure { id errorType errorMessage } skip { id } } } } }"
|
||||
)
|
||||
|
||||
|
||||
def request(method, url, headers, body=None):
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def main():
|
||||
secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
base = secrets.get("url", "").rstrip("/") + "/web/api/v2.1"
|
||||
headers = {
|
||||
"Authorization": "ApiToken " + secrets.get("api_token", ""),
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
variables = {"id": inputs.get("alert_id"), "verdict": inputs.get("analyst_verdict")}
|
||||
body = {"query": MUTATION, "variables": variables}
|
||||
print(json.dumps(request("POST", base + "/unifiedalerts/graphql", headers, body)))
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except urllib.error.HTTPError as e:
|
||||
print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}))
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user