diff --git a/integrations/cortex-xdr/manifest.yaml b/integrations/cortex-xdr/manifest.yaml new file mode 100644 index 0000000..f4fa70b --- /dev/null +++ b/integrations/cortex-xdr/manifest.yaml @@ -0,0 +1,270 @@ +id: cortex_xdr +name: Cortex XDR +version: 1.0.0 +description: "Palo Alto Cortex XDR (public API v1) — incident ingestion, endpoint isolation, RTR scripts, hash block/allow lists, file quarantine/restore, scans and alert retrieval." +changelog: "1.0.0 — Initial release: incident ingestion (get_incidents) with OCSF mapper, endpoints, isolate/unisolate, scan, hash blocklist/allowlist, quarantine/restore, run script + results, alerts retrieval, distributions and action status. Standard or Advanced API authentication." +category: endpoint + +# Per-instance configuration. The base URL is the tenant API root, e.g. +# https://api-yourtenant.xdr.eu.paloaltonetworks.com (scripts append /public_api/v1). +config_schema: + properties: + url: + type: string + description: "Cortex XDR API base URL, e.g. https://api-.xdr..paloaltonetworks.com" + api_key_id: + type: string + description: "API Key ID (the integer ID shown next to the API key in Settings → API Keys)" + api_key: + type: string + description: "API Key secret" + x-soar-sensitive: true + auth_type: + type: string + description: "Authentication method: standard (key sent as-is) or advanced (nonce + timestamp SHA-256 hash)" + default: standard + required: + - url + - api_key_id + - api_key + +# Documented for reference; the bundled scripts build the headers themselves. +# Standard: x-xdr-auth-id: , Authorization: +# Advanced: x-xdr-auth-id: , x-xdr-nonce, x-xdr-timestamp, +# Authorization: sha256(api_key + nonce + timestamp) +auth: + - id: apikey + type: api_key + in: header + name: Authorization + value_template: "{{secret}}" + secret_field: api_key + +commands: + # ── Ingestion ───────────────────────────────────────────────────────────── + - id: get_incidents + name: cortex-xdr-get-incidents + description: "Fetch Cortex XDR incidents for ingestion. Returns {reply:{incidents:[...]}}; use reply.incidents as the alert rule results path." + risk: read + inputs_schema: + properties: + status: { type: string, description: "Filter by status (e.g. new, under_investigation, resolved_threat_handled)" } + created_after: { type: string, description: "Lower bound on creation_time, ISO8601 or epoch ms (incremental fetch watermark)" } + limit: { type: number, description: "Maximum incidents to fetch (default 100)" } + required: [] + outputs_schema: { properties: {} } + ingest: + results_path: reply.incidents + dedup_key: incident_id + incremental_field: created_after + - id: get_incident_extra_data + name: cortex-xdr-get-incident-extra-data + description: "Get full incident data including its alerts and network artifacts by incident ID." + risk: read + inputs_schema: + properties: + incident_id: { type: string, description: "Incident ID" } + alerts_limit: { type: number, description: "Max alerts to return (default 1000)" } + required: [incident_id] + outputs_schema: { properties: {} } + - id: get_alerts + name: cortex-xdr-get-alerts + description: "Retrieve alerts using a custom filter (get_alerts_by_filter_data). Returns rich alert objects." + risk: read + inputs_schema: + properties: + severity: { type: string, description: "Comma-separated severities (low, medium, high, critical)" } + created_after: { type: string, description: "Lower bound on alert source_insert_ts, epoch ms" } + limit: { type: number, description: "Maximum alerts to fetch (default 100)" } + required: [] + outputs_schema: { properties: {} } + + # ── Endpoints ───────────────────────────────────────────────────────────── + - id: get_endpoints + name: cortex-xdr-get-endpoints + description: "List/search endpoints by hostname, IP, status, platform, group, etc." + risk: read + inputs_schema: + properties: + hostname: { type: string, description: "Hostname to filter by" } + ip_list: { type: string, description: "Comma-separated IPs" } + status: { type: string, description: "Endpoint status (connected, disconnected, ...)" } + platform: { type: string, description: "OS platform (windows, linux, macos, android)" } + limit: { type: number, description: "Max endpoints (default 30)" } + required: [] + outputs_schema: { properties: {} } + - id: isolate_endpoint + name: cortex-xdr-isolate-endpoint + description: "Network-isolate an endpoint by endpoint (agent) ID." + inputs_schema: + properties: + endpoint_id: { type: string, description: "Endpoint (agent) ID to isolate" } + incident_id: { type: string, description: "Optional incident ID to associate the action with" } + required: [endpoint_id] + outputs_schema: { properties: {} } + - id: unisolate_endpoint + name: cortex-xdr-unisolate-endpoint + description: "Remove network isolation from an endpoint by endpoint (agent) ID." + inputs_schema: + properties: + endpoint_id: { type: string, description: "Endpoint (agent) ID to unisolate" } + incident_id: { type: string, description: "Optional incident ID to associate the action with" } + required: [endpoint_id] + outputs_schema: { properties: {} } + - id: scan_endpoints + name: cortex-xdr-scan-endpoints + description: "Trigger a malware scan on endpoints matching the given filters (or all)." + inputs_schema: + properties: + endpoint_id_list: { type: string, description: "Comma-separated endpoint IDs (omit + use all=true to scan all)" } + hostname: { type: string, description: "Comma-separated hostnames" } + all: { type: boolean, description: "Scan all endpoints when true and no other filter is set" } + incident_id: { type: string, description: "Optional incident ID" } + required: [] + outputs_schema: { properties: {} } + - id: get_policy + name: cortex-xdr-get-policy + description: "Get the policy name applied to an endpoint." + risk: read + inputs_schema: + properties: + endpoint_id: { type: string, description: "Endpoint (agent) ID" } + required: [endpoint_id] + outputs_schema: { properties: {} } + + # ── Hash block / allow lists ────────────────────────────────────────────── + - id: blocklist_files + name: cortex-xdr-blocklist-files + description: "Add file hashes to the block list." + inputs_schema: + properties: + hash_list: { type: string, description: "Comma-separated SHA-256 hashes to block" } + comment: { type: string, description: "Optional comment" } + incident_id: { type: string, description: "Optional incident ID" } + required: [hash_list] + outputs_schema: { properties: {} } + - id: remove_blocklist_files + name: cortex-xdr-remove-blocklist-files + description: "Remove file hashes from the block list." + inputs_schema: + properties: + hash_list: { type: string, description: "Comma-separated SHA-256 hashes to remove" } + comment: { type: string, description: "Optional comment" } + incident_id: { type: string, description: "Optional incident ID" } + required: [hash_list] + outputs_schema: { properties: {} } + - id: allowlist_files + name: cortex-xdr-allowlist-files + description: "Add file hashes to the allow list." + inputs_schema: + properties: + hash_list: { type: string, description: "Comma-separated SHA-256 hashes to allow" } + comment: { type: string, description: "Optional comment" } + incident_id: { type: string, description: "Optional incident ID" } + required: [hash_list] + outputs_schema: { properties: {} } + - id: remove_allowlist_files + name: cortex-xdr-remove-allowlist-files + description: "Remove file hashes from the allow list." + inputs_schema: + properties: + hash_list: { type: string, description: "Comma-separated SHA-256 hashes to remove" } + comment: { type: string, description: "Optional comment" } + incident_id: { type: string, description: "Optional incident ID" } + required: [hash_list] + outputs_schema: { properties: {} } + + # ── File quarantine / restore ───────────────────────────────────────────── + - id: quarantine_files + name: cortex-xdr-quarantine-files + description: "Quarantine a file by path + hash on the given endpoints." + inputs_schema: + properties: + endpoint_id_list: { type: string, description: "Comma-separated endpoint IDs" } + file_path: { type: string, description: "Full path of the file to quarantine" } + file_hash: { type: string, description: "SHA-256 of the file" } + incident_id: { type: string, description: "Optional incident ID" } + required: [endpoint_id_list, file_path, file_hash] + outputs_schema: { properties: {} } + - id: restore_file + name: cortex-xdr-restore-file + description: "Restore a quarantined file by hash." + inputs_schema: + properties: + file_hash: { type: string, description: "SHA-256 of the file to restore" } + endpoint_id: { type: string, description: "Optional endpoint ID" } + incident_id: { type: string, description: "Optional incident ID" } + required: [file_hash] + outputs_schema: { properties: {} } + - id: get_quarantine_status + name: cortex-xdr-get-quarantine-status + description: "Check whether a file is quarantined on an endpoint." + risk: read + inputs_schema: + properties: + endpoint_id: { type: string, description: "Endpoint ID" } + file_path: { type: string, description: "File path" } + file_hash: { type: string, description: "SHA-256 of the file" } + required: [endpoint_id, file_path, file_hash] + outputs_schema: { properties: {} } + + # ── Real-time scripts ───────────────────────────────────────────────────── + - id: run_script + name: cortex-xdr-run-script + description: "Run a script (by UID) on endpoints with optional parameters." + inputs_schema: + properties: + script_uid: { type: string, description: "Script UID" } + endpoint_ids: { type: string, description: "Comma-separated endpoint IDs" } + parameters: { type: string, description: "JSON object of parameter values" } + timeout: { type: number, description: "Timeout in seconds (default 600)" } + incident_id: { type: string, description: "Optional incident ID" } + required: [script_uid, endpoint_ids] + outputs_schema: { properties: {} } + - id: get_script_execution_results + name: cortex-xdr-get-script-execution-results + description: "Get the results of a script execution by action ID." + risk: read + inputs_schema: + properties: + action_id: { type: string, description: "Action ID returned by run_script" } + required: [action_id] + outputs_schema: { properties: {} } + - id: get_scripts + name: cortex-xdr-get-scripts + description: "List available scripts, optionally filtered by name/description/OS support." + risk: read + inputs_schema: + properties: + name: { type: string, description: "Comma-separated script names" } + windows_supported: { type: boolean, description: "Filter scripts supporting Windows" } + required: [] + outputs_schema: { properties: {} } + + # ── Actions / misc ──────────────────────────────────────────────────────── + - id: action_status + name: cortex-xdr-action-status + description: "Get the status of a group action by action ID." + risk: read + inputs_schema: + properties: + action_id: { type: string, description: "Group action ID" } + required: [action_id] + outputs_schema: { properties: {} } + - id: get_distribution_versions + name: cortex-xdr-get-distribution-versions + description: "List available agent installer versions." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + + - id: test_connection + name: cortex-xdr-test-connection + description: "Verify connectivity and credentials (used by the Test button)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } diff --git a/integrations/cortex-xdr/mappers/get_incidents.yaml b/integrations/cortex-xdr/mappers/get_incidents.yaml new file mode 100644 index 0000000..a3023ed --- /dev/null +++ b/integrations/cortex-xdr/mappers/get_incidents.yaml @@ -0,0 +1,24 @@ +name: "Cortex XDR Incidents → OCSF" +description: "Maps a Cortex XDR incident (incidents/get_incidents/, results_path = reply.incidents) to OCSF finding fields. Incidents are aggregates; use get_incident_extra_data for per-alert detail." +field_mappings: + title: "incident_name" + severity: "severity = 'critical' ? 5 : (severity = 'high' ? 4 : (severity = 'medium' ? 3 : 2))" + description: "description" +# results_path = reply.incidents; source_path is JSONata over ONE incident object. +# Paths absent from a given incident are skipped at ingestion, so extra entries are safe. +ocsf: + # ── Finding ─────────────────────────────────────────────────────── + - { source_path: "incident_id", ocsf_field: "finding_info.uid" } + - { source_path: "incident_name", ocsf_field: "finding_info.title" } + - { source_path: "description", ocsf_field: "finding_info.desc" } + - { source_path: "creation_time", ocsf_field: "finding_info.created_time" } + - { source_path: "modification_time", ocsf_field: "finding_info.modified_time" } + - { source_path: "xdr_url", ocsf_field: "finding_info.src_url" } + - { source_path: "status", ocsf_field: "status" } + - { source_path: "alert_count", ocsf_field: "count" } + # ── MITRE ATT&CK (first aggregated tactic/technique) ────────────── + - { source_path: "mitre_tactics_ids_and_names[0]", ocsf_field: "attacks.tactic.name" } + - { source_path: "mitre_techniques_ids_and_names[0]", ocsf_field: "attacks.technique.name" } + # ── Affected host / user (hosts are 'hostname:agent_id' strings) ── + - { source_path: "$split(hosts[0], ':')[0]", ocsf_field: "src_endpoint.hostname" } + - { source_path: "users[0]", ocsf_field: "user.name" } diff --git a/integrations/cortex-xdr/scripts/action_status.py b/integrations/cortex-xdr/scripts/action_status.py new file mode 100644 index 0000000..8a50e80 --- /dev/null +++ b/integrations/cortex-xdr/scripts/action_status.py @@ -0,0 +1,42 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def main(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + print(json.dumps(post("/actions/get_action_status/", {"group_action_id": inputs.get("action_id")}))) + + +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) diff --git a/integrations/cortex-xdr/scripts/allowlist_files.py b/integrations/cortex-xdr/scripts/allowlist_files.py new file mode 100644 index 0000000..959e8fa --- /dev/null +++ b/integrations/cortex-xdr/scripts/allowlist_files.py @@ -0,0 +1,51 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.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(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + rd = {"hash_list": csv(inputs.get("hash_list"))} + if inputs.get("comment"): + rd["comment"] = inputs["comment"] + if inputs.get("incident_id"): + rd["incident_id"] = inputs["incident_id"] + print(json.dumps(post("/hash_exceptions/allowlist/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/blocklist_files.py b/integrations/cortex-xdr/scripts/blocklist_files.py new file mode 100644 index 0000000..f64469c --- /dev/null +++ b/integrations/cortex-xdr/scripts/blocklist_files.py @@ -0,0 +1,51 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.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(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + rd = {"hash_list": csv(inputs.get("hash_list"))} + if inputs.get("comment"): + rd["comment"] = inputs["comment"] + if inputs.get("incident_id"): + rd["incident_id"] = inputs["incident_id"] + print(json.dumps(post("/hash_exceptions/blocklist/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/get_alerts.py b/integrations/cortex-xdr/scripts/get_alerts.py new file mode 100644 index 0000000..c8fc9b2 --- /dev/null +++ b/integrations/cortex-xdr/scripts/get_alerts.py @@ -0,0 +1,69 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error +from datetime import datetime + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.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 to_ms(v): + if v in (None, ""): + return None + s = str(v) + if s.isdigit(): + return int(s) + try: + return int(datetime.fromisoformat(s.replace("Z", "+00:00")).timestamp() * 1000) + except Exception: + return None + + +def main(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + limit = int(inputs.get("limit") or 100) + filters = [] + if inputs.get("severity"): + filters.append({"field": "severity", "operator": "in", "value": csv(inputs["severity"])}) + created_ms = to_ms(inputs.get("created_after")) + if created_ms is not None: + filters.append({"field": "source_insert_ts", "operator": "gte", "value": created_ms}) + rd = {"search_from": 0, "search_to": limit, "sort": {"field": "source_insert_ts", "keyword": "desc"}} + if filters: + rd["filters"] = filters + print(json.dumps(post("/alerts/get_alerts_by_filter_data/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/get_distribution_versions.py b/integrations/cortex-xdr/scripts/get_distribution_versions.py new file mode 100644 index 0000000..e4fdb4a --- /dev/null +++ b/integrations/cortex-xdr/scripts/get_distribution_versions.py @@ -0,0 +1,41 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def main(): + print(json.dumps(post("/distributions/get_versions/", {}))) + + +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) diff --git a/integrations/cortex-xdr/scripts/get_endpoints.py b/integrations/cortex-xdr/scripts/get_endpoints.py new file mode 100644 index 0000000..87ac1b9 --- /dev/null +++ b/integrations/cortex-xdr/scripts/get_endpoints.py @@ -0,0 +1,56 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.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(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + limit = int(inputs.get("limit") or 30) + filters = [] + if inputs.get("hostname"): + filters.append({"field": "hostname", "operator": "in", "value": csv(inputs["hostname"])}) + if inputs.get("ip_list"): + filters.append({"field": "ip_list", "operator": "in", "value": csv(inputs["ip_list"])}) + if inputs.get("status"): + filters.append({"field": "endpoint_status", "operator": "IN", "value": csv(inputs["status"])}) + if inputs.get("platform"): + filters.append({"field": "platform", "operator": "in", "value": csv(inputs["platform"])}) + print(json.dumps(post("/endpoints/get_endpoint/", {"search_from": 0, "search_to": limit, "filters": filters}))) + + +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) diff --git a/integrations/cortex-xdr/scripts/get_incident_extra_data.py b/integrations/cortex-xdr/scripts/get_incident_extra_data.py new file mode 100644 index 0000000..301a1fb --- /dev/null +++ b/integrations/cortex-xdr/scripts/get_incident_extra_data.py @@ -0,0 +1,43 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def main(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + rd = {"incident_id": inputs.get("incident_id"), "alerts_limit": int(inputs.get("alerts_limit") or 1000)} + print(json.dumps(post("/incidents/get_incident_extra_data/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/get_incidents.py b/integrations/cortex-xdr/scripts/get_incidents.py new file mode 100644 index 0000000..8789ec9 --- /dev/null +++ b/integrations/cortex-xdr/scripts/get_incidents.py @@ -0,0 +1,69 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error +from datetime import datetime + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def to_ms(v): + if v in (None, ""): + return None + s = str(v) + if s.isdigit(): + return int(s) + try: + return int(datetime.fromisoformat(s.replace("Z", "+00:00")).timestamp() * 1000) + except Exception: + return None + + +def main(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + limit = int(inputs.get("limit") or 100) + filters = [] + if inputs.get("status"): + filters.append({"field": "status", "operator": "eq", "value": inputs["status"]}) + created_ms = to_ms(inputs.get("created_after")) + if created_ms is not None: + filters.append({"field": "creation_time", "operator": "gte", "value": created_ms}) + request_data = { + "search_from": 0, + "search_to": limit, + "sort": {"field": "creation_time", "keyword": "desc"}, + } + if filters: + request_data["filters"] = filters + print(json.dumps(post("/incidents/get_incidents/", request_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) diff --git a/integrations/cortex-xdr/scripts/get_policy.py b/integrations/cortex-xdr/scripts/get_policy.py new file mode 100644 index 0000000..32ec446 --- /dev/null +++ b/integrations/cortex-xdr/scripts/get_policy.py @@ -0,0 +1,42 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def main(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + print(json.dumps(post("/endpoints/get_policy/", {"endpoint_id": inputs.get("endpoint_id")}))) + + +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) diff --git a/integrations/cortex-xdr/scripts/get_quarantine_status.py b/integrations/cortex-xdr/scripts/get_quarantine_status.py new file mode 100644 index 0000000..083a362 --- /dev/null +++ b/integrations/cortex-xdr/scripts/get_quarantine_status.py @@ -0,0 +1,43 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def main(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + rd = {"files": [{"endpoint_id": inputs.get("endpoint_id"), "file_path": inputs.get("file_path"), "file_hash": inputs.get("file_hash")}]} + print(json.dumps(post("/quarantine/status/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/get_script_execution_results.py b/integrations/cortex-xdr/scripts/get_script_execution_results.py new file mode 100644 index 0000000..457d376 --- /dev/null +++ b/integrations/cortex-xdr/scripts/get_script_execution_results.py @@ -0,0 +1,42 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def main(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + print(json.dumps(post("/scripts/get_script_execution_results", {"action_id": inputs.get("action_id")}))) + + +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) diff --git a/integrations/cortex-xdr/scripts/get_scripts.py b/integrations/cortex-xdr/scripts/get_scripts.py new file mode 100644 index 0000000..cff3417 --- /dev/null +++ b/integrations/cortex-xdr/scripts/get_scripts.py @@ -0,0 +1,52 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.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(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + filters = [] + if inputs.get("name"): + filters.append({"field": "name", "operator": "in", "value": csv(inputs["name"])}) + ws = inputs.get("windows_supported") + if ws not in (None, ""): + filters.append({"field": "windows_supported", "operator": "in", "value": [bool(ws)]}) + print(json.dumps(post("/scripts/get_scripts/", {"filters": filters}))) + + +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) diff --git a/integrations/cortex-xdr/scripts/isolate_endpoint.py b/integrations/cortex-xdr/scripts/isolate_endpoint.py new file mode 100644 index 0000000..9d49b2d --- /dev/null +++ b/integrations/cortex-xdr/scripts/isolate_endpoint.py @@ -0,0 +1,45 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def main(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + rd = {"endpoint_id": inputs.get("endpoint_id")} + if inputs.get("incident_id"): + rd["incident_id"] = inputs["incident_id"] + print(json.dumps(post("/endpoints/isolate", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/quarantine_files.py b/integrations/cortex-xdr/scripts/quarantine_files.py new file mode 100644 index 0000000..b94045f --- /dev/null +++ b/integrations/cortex-xdr/scripts/quarantine_files.py @@ -0,0 +1,53 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.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(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + rd = { + "filters": [{"field": "endpoint_id_list", "operator": "in", "value": csv(inputs.get("endpoint_id_list"))}], + "file_path": inputs.get("file_path"), + "file_hash": inputs.get("file_hash"), + } + if inputs.get("incident_id"): + rd["incident_id"] = inputs["incident_id"] + print(json.dumps(post("/endpoints/quarantine/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/remove_allowlist_files.py b/integrations/cortex-xdr/scripts/remove_allowlist_files.py new file mode 100644 index 0000000..11c0fe2 --- /dev/null +++ b/integrations/cortex-xdr/scripts/remove_allowlist_files.py @@ -0,0 +1,51 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.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(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + rd = {"hash_list": csv(inputs.get("hash_list"))} + if inputs.get("comment"): + rd["comment"] = inputs["comment"] + if inputs.get("incident_id"): + rd["incident_id"] = inputs["incident_id"] + print(json.dumps(post("/hash_exceptions/allowlist/remove/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/remove_blocklist_files.py b/integrations/cortex-xdr/scripts/remove_blocklist_files.py new file mode 100644 index 0000000..e0f34c4 --- /dev/null +++ b/integrations/cortex-xdr/scripts/remove_blocklist_files.py @@ -0,0 +1,51 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.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(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + rd = {"hash_list": csv(inputs.get("hash_list"))} + if inputs.get("comment"): + rd["comment"] = inputs["comment"] + if inputs.get("incident_id"): + rd["incident_id"] = inputs["incident_id"] + print(json.dumps(post("/hash_exceptions/blocklist/remove/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/restore_file.py b/integrations/cortex-xdr/scripts/restore_file.py new file mode 100644 index 0000000..01133f4 --- /dev/null +++ b/integrations/cortex-xdr/scripts/restore_file.py @@ -0,0 +1,47 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def main(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + rd = {"file_hash": inputs.get("file_hash")} + if inputs.get("endpoint_id"): + rd["endpoint_id"] = inputs["endpoint_id"] + if inputs.get("incident_id"): + rd["incident_id"] = inputs["incident_id"] + print(json.dumps(post("/endpoints/restore/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/run_script.py b/integrations/cortex-xdr/scripts/run_script.py new file mode 100644 index 0000000..214a705 --- /dev/null +++ b/integrations/cortex-xdr/scripts/run_script.py @@ -0,0 +1,58 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.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(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + try: + params = json.loads(inputs.get("parameters") or "{}") + except Exception: + params = {} + rd = { + "script_uid": inputs.get("script_uid"), + "timeout": int(inputs.get("timeout") or 600), + "filters": [{"field": "endpoint_id_list", "operator": "in", "value": csv(inputs.get("endpoint_ids"))}], + "parameters_values": params, + } + if inputs.get("incident_id"): + rd["incident_id"] = inputs["incident_id"] + print(json.dumps(post("/scripts/run_script/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/scan_endpoints.py b/integrations/cortex-xdr/scripts/scan_endpoints.py new file mode 100644 index 0000000..d8826f2 --- /dev/null +++ b/integrations/cortex-xdr/scripts/scan_endpoints.py @@ -0,0 +1,54 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.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(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + filters = [] + if inputs.get("endpoint_id_list"): + filters.append({"field": "endpoint_id_list", "operator": "in", "value": csv(inputs["endpoint_id_list"])}) + if inputs.get("hostname"): + filters.append({"field": "hostname", "operator": "in", "value": csv(inputs["hostname"])}) + rd = {"filters": filters if filters else "all"} + if inputs.get("incident_id"): + rd["incident_id"] = inputs["incident_id"] + print(json.dumps(post("/endpoints/scan/", rd))) + + +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) diff --git a/integrations/cortex-xdr/scripts/test_connection.py b/integrations/cortex-xdr/scripts/test_connection.py new file mode 100644 index 0000000..e4fdb4a --- /dev/null +++ b/integrations/cortex-xdr/scripts/test_connection.py @@ -0,0 +1,41 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def main(): + print(json.dumps(post("/distributions/get_versions/", {}))) + + +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) diff --git a/integrations/cortex-xdr/scripts/unisolate_endpoint.py b/integrations/cortex-xdr/scripts/unisolate_endpoint.py new file mode 100644 index 0000000..ccd76ce --- /dev/null +++ b/integrations/cortex-xdr/scripts/unisolate_endpoint.py @@ -0,0 +1,45 @@ +import json, os, sys, time, hashlib, secrets, string, urllib.request, urllib.error + + +def _client(): + s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + base = s.get("url", "").rstrip("/") + "/public_api/v1" + key = s.get("api_key", "") + kid = str(s.get("api_key_id", "")) + headers = {"x-xdr-auth-id": kid, "Content-Type": "application/json", "Accept": "application/json"} + if str(s.get("auth_type") or "standard").lower() == "advanced": + nonce = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(64)) + ts = str(int(time.time()) * 1000) + headers["x-xdr-nonce"] = nonce + headers["x-xdr-timestamp"] = ts + headers["Authorization"] = hashlib.sha256((key + nonce + ts).encode("utf-8")).hexdigest() + else: + headers["Authorization"] = key + return base, headers + + +def post(path, request_data): + base, headers = _client() + data = json.dumps({"request_data": request_data}).encode("utf-8") + req = urllib.request.Request(base + path, data=data, headers=headers, method="POST") + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def main(): + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + rd = {"endpoint_id": inputs.get("endpoint_id")} + if inputs.get("incident_id"): + rd["incident_id"] = inputs["incident_id"] + print(json.dumps(post("/endpoints/unisolate", rd))) + + +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)