From 5251441962fefa0d8860ea724833a3958f8672b5 Mon Sep 17 00:00:00 2001 From: Guillaume BOURGEOIS Date: Fri, 26 Jun 2026 00:00:06 +0200 Subject: [PATCH] feat: add test_connection command to all integrations for the instance Test button Co-Authored-By: Claude Opus 4.8 --- integrations/crowdstrike/manifest.yaml | 14 ++++++-- .../crowdstrike/scripts/test_connection.py | 32 +++++++++++++++++ integrations/harfanglab/manifest.yaml | 14 ++++++-- .../harfanglab/scripts/test_connection.py | 34 ++++++++++++++++++ integrations/sentinelone/manifest.yaml | 14 ++++++-- .../sentinelone/scripts/test_connection.py | 35 +++++++++++++++++++ integrations/shodan/manifest.yaml | 18 ++++++++-- integrations/virustotal/manifest.yaml | 18 ++++++++-- 8 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 integrations/crowdstrike/scripts/test_connection.py create mode 100644 integrations/harfanglab/scripts/test_connection.py create mode 100644 integrations/sentinelone/scripts/test_connection.py diff --git a/integrations/crowdstrike/manifest.yaml b/integrations/crowdstrike/manifest.yaml index d06891c..aa1e2da 100644 --- a/integrations/crowdstrike/manifest.yaml +++ b/integrations/crowdstrike/manifest.yaml @@ -1,8 +1,8 @@ id: crowdstrike name: CrowdStrike Falcon -version: 1.1.0 +version: 1.1.1 description: "CrowdStrike Falcon (OAuth2 API) — full IR coverage: device/IOC/process enrichment, detections & cases, host groups, Real Time Response, ML/IOA exclusions, quarantine, Spotlight/CVE, ODS scans, CSPM, users, IOA rules, CNAPP, and Fusion workflows." -changelog: "1.1.0 — Expanded to 81 commands (host groups, cases, RTR files/scripts/responders, ML/IOA exclusions, quarantine, Spotlight host-by-vuln/CVE, ODS scans, CSPM, users, IOA rules, CNAPP, identity/mobile detection resolve, and workflows). 1.0.0 — Initial release: device/detection enrichment, Spotlight, IOC management, contain/lift, and core RTR." +changelog: "1.1.1 — Added test_connection for the instance Test button. 1.1.0 — Expanded to 81 commands (host groups, cases, RTR files/scripts/responders, ML/IOA exclusions, quarantine, Spotlight host-by-vuln/CVE, ODS scans, CSPM, users, IOA rules, CNAPP, identity/mobile detection resolve, and workflows). 1.0.0 — Initial release: device/detection enrichment, Spotlight, IOC management, contain/lift, and core RTR." category: endpoint # Per-instance configuration. Scripts obtain an OAuth2 bearer token from @@ -1033,3 +1033,13 @@ commands: email: { type: string, description: "Optional filter by email address (informational; include in query if supported)." } required: [type] outputs_schema: { properties: {} } + + # ── Connectivity test ───────────────────────────────────────────────────── + - id: test_connection + name: crowdstrike-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/crowdstrike/scripts/test_connection.py b/integrations/crowdstrike/scripts/test_connection.py new file mode 100644 index 0000000..c86f252 --- /dev/null +++ b/integrations/crowdstrike/scripts/test_connection.py @@ -0,0 +1,32 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def main(): + # Acquiring the OAuth2 token validates client_id/client_secret: bad + # credentials make the exchange fail (HTTPError -> exit 1). + tok = token() + if not tok: + print(json.dumps({"ok": False, "error": "No access token returned"})) + sys.exit(1) + print(json.dumps({"ok": True, "authenticated": True})) + + +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/harfanglab/manifest.yaml b/integrations/harfanglab/manifest.yaml index 3316891..8aedc6d 100644 --- a/integrations/harfanglab/manifest.yaml +++ b/integrations/harfanglab/manifest.yaml @@ -1,8 +1,8 @@ id: harfanglab name: HarfangLab EDR -version: 1.1.0 +version: 1.1.1 description: "HarfangLab EDR — endpoint detection & response: endpoint enrichment, isolation, threat-intelligence (IOC/whitelist), telemetry hunting and forensic collection jobs." -changelog: "1.1.0 — Command names prefixed with 'harfanglab-' (e.g. harfanglab-isolate-endpoint) for easier toolbox search; command IDs unchanged. 1.0.0 — Initial release: endpoint/agent management, isolation, policy assignment, IOC & whitelist management, security-event triage, telemetry hunting (processes, network, DNS, authentications, binaries, event logs), threat hunting by hash, and forensic collection jobs (pipes, prefetch, run keys, scheduled tasks, drivers, services, processes, network, sessions, WMI, IOC scan, artifacts, RAM dump) with their result retrieval commands. Compatible with HarfangLab EDR 2.13.7+." +changelog: "1.1.1 — Added test_connection for the instance Test button. 1.1.0 — Command names prefixed with 'harfanglab-' (e.g. harfanglab-isolate-endpoint) for easier toolbox search; command IDs unchanged. 1.0.0 — Initial release: endpoint/agent management, isolation, policy assignment, IOC & whitelist management, security-event triage, telemetry hunting (processes, network, DNS, authentications, binaries, event logs), threat hunting by hash, and forensic collection jobs (pipes, prefetch, run keys, scheduled tasks, drivers, services, processes, network, sessions, WMI, IOC scan, artifacts, RAM dump) with their result retrieval commands. Compatible with HarfangLab EDR 2.13.7+." category: endpoint # Per-instance configuration. Scripts use as the API base and call /api/... paths. @@ -698,3 +698,13 @@ commands: job_id: { type: string, description: "Job ID returned by the matching artifact job command" } required: [job_id] outputs_schema: { properties: {} } + + # ── Connectivity test ───────────────────────────────────────────────────── + - id: test_connection + name: harfanglab-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/harfanglab/scripts/test_connection.py b/integrations/harfanglab/scripts/test_connection.py new file mode 100644 index 0000000..1110407 --- /dev/null +++ b/integrations/harfanglab/scripts/test_connection.py @@ -0,0 +1,34 @@ +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 main(): + secrets = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + inputs = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + base = secrets.get("url", "").rstrip("/") + headers = { + "Authorization": "Token " + secrets.get("api_token", ""), + "Accept": "application/json", + "Content-Type": "application/json", + } + # === per-command logic === + # Lightweight authenticated GET; a bad token returns 401/403 -> exit 1. + request("GET", base + "/api/version", headers) + print(json.dumps({"ok": True})) + + +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/sentinelone/manifest.yaml b/integrations/sentinelone/manifest.yaml index cac3a1d..2bb6866 100644 --- a/integrations/sentinelone/manifest.yaml +++ b/integrations/sentinelone/manifest.yaml @@ -1,8 +1,8 @@ id: sentinelone name: SentinelOne -version: 1.2.0 +version: 1.2.1 description: "SentinelOne Singularity (API v2.1) — endpoint detection & response: triage threats, enrich, isolate/reconnect hosts, mitigate, scan." -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." +changelog: "1.2.1 — Added test_connection for the instance Test button. 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 /web/api/v2.1. @@ -955,3 +955,13 @@ commands: description: { type: string, description: "Rule description." } required: [name, account_id, tag_id, filter_values] outputs_schema: { properties: {} } + + # ── Connectivity test ───────────────────────────────────────────────────── + - id: test_connection + name: sentinelone-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/sentinelone/scripts/test_connection.py b/integrations/sentinelone/scripts/test_connection.py new file mode 100644 index 0000000..366e7c2 --- /dev/null +++ b/integrations/sentinelone/scripts/test_connection.py @@ -0,0 +1,35 @@ +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", + } + # === REQUEST === + # Lightweight authenticated GET; a bad token returns 401 -> exit 1. + request("GET", base + "/system/info", headers) + print(json.dumps({"ok": True})) + # === END === + + +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/shodan/manifest.yaml b/integrations/shodan/manifest.yaml index 22e55e5..4521e60 100644 --- a/integrations/shodan/manifest.yaml +++ b/integrations/shodan/manifest.yaml @@ -1,8 +1,8 @@ id: shodan name: Shodan -version: 1.0.0 +version: 1.0.1 description: "Shodan — host & network intelligence: IP enrichment, search, DNS lookups, domain info, and on-demand scanning." -changelog: "1.0.0 — Initial release: host lookup, search, count, DNS resolve/reverse, domain info, api-info, scan status, and active scan." +changelog: "1.0.1 — Added test_connection for the instance Test button. 1.0.0 — Initial release: host lookup, search, count, DNS resolve/reverse, domain info, api-info, scan status, and active scan." category: enrichment config_schema: @@ -158,6 +158,20 @@ commands: # application/x-www-form-urlencoded body, which the form-based generator # (JSON body) cannot produce. Marked destructive: it triggers an active, # outbound crawl of the target and consumes scan credits. + # ── Connectivity test ───────────────────────────────────────────────────── + - id: test_connection + name: shodan-test-connection + description: "Verify connectivity and credentials (used by the Test button)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + request: + method: GET + path: /api-info + auth_ref: apikey + - id: scan name: shodan-scan description: Request Shodan to actively crawl an IP or netblock (consumes scan credits). diff --git a/integrations/virustotal/manifest.yaml b/integrations/virustotal/manifest.yaml index 2481f5a..652268c 100644 --- a/integrations/virustotal/manifest.yaml +++ b/integrations/virustotal/manifest.yaml @@ -1,8 +1,8 @@ id: virustotal name: VirusTotal -version: 1.0.0 +version: 1.0.1 description: VirusTotal API v3 — reputation lookups for IPs and domains. -changelog: "1.0.0 — Initial release: IP and domain reputation lookups." +changelog: "1.0.1 — Added test_connection for the instance Test button. 1.0.0 — Initial release: IP and domain reputation lookups." category: enrichment config_schema: @@ -60,3 +60,17 @@ commands: method: GET path: /domains/{domain} auth_ref: apikey + + - id: test_connection + name: virustotal-test-connection + description: "Verify connectivity and credentials (used by the Test button)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: + properties: {} + request: + method: GET + path: /ip_addresses/8.8.8.8 + auth_ref: apikey