Compare commits
2 Commits
70dffb3b0a
...
7e96048446
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e96048446 | |||
| 934f2c52d7 |
@@ -0,0 +1,3 @@
|
||||
name: "Mock EDR Incident"
|
||||
color: "#00a8a8"
|
||||
icon: "alert"
|
||||
@@ -0,0 +1,78 @@
|
||||
id: mock_edr_s1
|
||||
name: Mock EDR (S1)
|
||||
version: 1.0.0
|
||||
description: "Mock EDR API (instance s1) — incident ingestion with since/after_id pagination and incident actions (acknowledge / resolve / dismiss). Built from the published OpenAPI spec."
|
||||
changelog: "1.0.0 — Initial release: incident ingestion (list_incidents) with an OCSF mapper, plus an acknowledge/resolve/dismiss action."
|
||||
category: endpoint
|
||||
|
||||
# Per-instance configuration. The API key is sent in the X-API-Key header. The
|
||||
# instance segment of the path (/api/<instance>/incidents) is configurable.
|
||||
config_schema:
|
||||
properties:
|
||||
base_url:
|
||||
type: string
|
||||
description: "API base URL"
|
||||
default: https://mockprod.riposte-labs.com
|
||||
instance:
|
||||
type: string
|
||||
description: "Instance name used in the path (/api/<instance>/incidents)"
|
||||
default: s1
|
||||
api_key:
|
||||
type: string
|
||||
description: "API key (X-API-Key)"
|
||||
x-soar-sensitive: true
|
||||
required:
|
||||
- base_url
|
||||
- api_key
|
||||
|
||||
auth:
|
||||
- id: apikey
|
||||
type: api_key
|
||||
in: header
|
||||
name: X-API-Key
|
||||
value_template: "{{secret}}"
|
||||
secret_field: api_key
|
||||
|
||||
commands:
|
||||
# ── Ingestion ───────────────────────────────────────────────────────────────
|
||||
- id: list_incidents
|
||||
name: mock-edr-s1-list-incidents
|
||||
description: "List incidents. Used for ingestion: results path = items. Supports incremental fetch via 'since' and cursor paging via 'after_id'."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
since: { type: string, description: "Return incidents created after this ISO-8601 timestamp. Incremental fetch watermark." }
|
||||
after_id: { type: number, description: "Return incidents with ID greater than this value (cursor paging)" }
|
||||
limit: { type: number, description: "Maximum number of incidents (default 100, max 1000)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
ingest:
|
||||
results_path: items
|
||||
dedup_key: id
|
||||
incremental_field: since
|
||||
|
||||
- id: incident_action
|
||||
name: mock-edr-s1-incident-action
|
||||
description: "Acknowledge, resolve or dismiss an incident."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
id: { type: number, description: "Incident ID" }
|
||||
action: { type: string, description: "Action to apply: acknowledge, resolve or dismiss" }
|
||||
required: [id, action]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Connectivity test ─────────────────────────────────────────────────────
|
||||
- id: test_connection
|
||||
name: mock-edr-s1-test-connection
|
||||
description: "Verify connectivity and credentials (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
ingestion:
|
||||
command: list_incidents
|
||||
mapper: list_incidents
|
||||
default_incident_type: "Mock EDR Incident"
|
||||
@@ -0,0 +1,23 @@
|
||||
name: "Mock EDR Incidents → OCSF"
|
||||
description: "Maps a Mock EDR incident (/api/<instance>/incidents, results_path = items) to OCSF Detection Finding fields."
|
||||
field_mappings:
|
||||
title: "title"
|
||||
description: "description"
|
||||
source: "source"
|
||||
# toSeverity maps critical→5, high→3, medium→2, low→1, informational→1.
|
||||
severity: "severity"
|
||||
# results_path = items; source_path is JSONata over ONE incident object.
|
||||
ocsf:
|
||||
# ── Finding ───────────────────────────────────────────────────────
|
||||
- { source_path: "id", ocsf_field: "finding_info.uid" }
|
||||
- { source_path: "external_id", ocsf_field: "metadata.uid" }
|
||||
- { source_path: "title", ocsf_field: "finding_info.title" }
|
||||
- { source_path: "description", ocsf_field: "finding_info.desc" }
|
||||
- { source_path: "created_at", ocsf_field: "finding_info.created_time" }
|
||||
- { source_path: "status", ocsf_field: "status" }
|
||||
- { source_path: "source", ocsf_field: "metadata.product.name" }
|
||||
# ── Affected host / artefact ──────────────────────────────────────
|
||||
- { source_path: "hostname", ocsf_field: "src_endpoint.hostname" }
|
||||
- { source_path: "ip_address", ocsf_field: "src_endpoint.ip" }
|
||||
- { source_path: "hostname", ocsf_field: "device.hostname" }
|
||||
- { source_path: "file_path", ocsf_field: "file.path" }
|
||||
@@ -0,0 +1,40 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://mockprod.riposte-labs.com").rstrip("/")
|
||||
instance = str(s.get("instance") or "s1").strip("/")
|
||||
headers = {"X-API-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
return base, instance, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, body=None):
|
||||
base, _, headers = _cfg()
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
_, instance, _ = _cfg()
|
||||
inp = _inputs()
|
||||
inc_id = urllib.parse.quote(str(inp.get("id", "")), safe="")
|
||||
body = {"action": inp.get("action")}
|
||||
print(json.dumps(request("POST", "api/" + instance + "/incidents/" + inc_id + "/actions", body=body)))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,40 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://mockprod.riposte-labs.com").rstrip("/")
|
||||
instance = str(s.get("instance") or "s1").strip("/")
|
||||
headers = {"X-API-Key": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, instance, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path, params):
|
||||
base, _, headers = _cfg()
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
url = base + "/" + path.lstrip("/") + ("?" + urllib.parse.urlencode(clean, doseq=True) if clean else "")
|
||||
req = urllib.request.Request(url, headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
_, instance, _ = _cfg()
|
||||
inp = _inputs()
|
||||
params = {"since": inp.get("since"), "after_id": inp.get("after_id"), "limit": min(int(inp.get("limit") or 100), 1000)}
|
||||
print(json.dumps(request("api/" + instance + "/incidents", params)))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,37 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://mockprod.riposte-labs.com").rstrip("/")
|
||||
instance = str(s.get("instance") or "s1").strip("/")
|
||||
headers = {"X-API-Key": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, instance, headers
|
||||
|
||||
|
||||
def request(path, params):
|
||||
base, _, headers = _cfg()
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
url = base + "/" + path.lstrip("/") + ("?" + urllib.parse.urlencode(clean, doseq=True) if clean else "")
|
||||
req = urllib.request.Request(url, headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
_, instance, _ = _cfg()
|
||||
request("api/" + instance + "/incidents", {"limit": 1})
|
||||
print(json.dumps({"ok": True}))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
except urllib.error.HTTPError as e:
|
||||
detail = e.read().decode("utf-8", "replace")
|
||||
msg = "API key is not valid." if e.code in (401, 403) else "HTTP " + str(e.code)
|
||||
print(json.dumps({"ok": False, "error": msg, "detail": detail}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"ok": False, "error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -1,8 +1,8 @@
|
||||
id: virustotal
|
||||
name: VirusTotal
|
||||
version: 1.0.2
|
||||
description: VirusTotal API v3 — reputation lookups for IPs and domains.
|
||||
changelog: "1.0.2 — Re-publish to regenerate form-based command scripts (fixes 'script not found' on install). 1.0.1 — Added test_connection for the instance Test button. 1.0.0 — Initial release: IP and domain reputation lookups."
|
||||
version: 1.1.0
|
||||
description: "VirusTotal API v3 — reputation for IPs, domains, files and URLs, plus rescan/scan, intelligence search, analysis results, file behaviour (sandbox) reports, passive DNS and comments."
|
||||
changelog: "1.1.0 — Full v3 coverage: added file and URL reputation, file rescan, URL scan, search, analysis-get, file sandbox (behaviour) report, passive DNS and comments (get/add/get-by-id/delete). Commands are now script-based (URL base64, form-encoded scans, comment resource routing). 1.0.2 — Re-publish to regenerate form-based command scripts. 1.0.1 — Added test_connection. 1.0.0 — Initial release: IP and domain reputation lookups."
|
||||
category: enrichment
|
||||
|
||||
config_schema:
|
||||
@@ -27,40 +27,158 @@ auth:
|
||||
secret_field: api_key
|
||||
|
||||
commands:
|
||||
# ── Reputation ──────────────────────────────────────────────────────────────
|
||||
- id: get_ip_report
|
||||
name: Get IP report
|
||||
description: Reputation and last-analysis stats for an IP address.
|
||||
name: virustotal-ip
|
||||
description: "Reputation and last-analysis stats for an IP address."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
ip:
|
||||
type: string
|
||||
description: IP address to look up
|
||||
required:
|
||||
- ip
|
||||
outputs_schema:
|
||||
properties: {}
|
||||
request:
|
||||
method: GET
|
||||
path: /ip_addresses/{ip}
|
||||
auth_ref: apikey
|
||||
ip: { type: string, description: "IP address to look up" }
|
||||
required: [ip]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: get_domain_report
|
||||
name: Get domain report
|
||||
description: Reputation and last-analysis stats for a domain.
|
||||
name: virustotal-domain
|
||||
description: "Reputation and last-analysis stats for a domain."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
domain:
|
||||
type: string
|
||||
description: Domain to look up
|
||||
required:
|
||||
- domain
|
||||
outputs_schema:
|
||||
properties: {}
|
||||
request:
|
||||
method: GET
|
||||
path: /domains/{domain}
|
||||
auth_ref: apikey
|
||||
domain: { type: string, description: "Domain to look up" }
|
||||
required: [domain]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: file
|
||||
name: virustotal-file
|
||||
description: "Reputation and analysis for a file hash (MD5, SHA1, SHA256)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
file: { type: string, description: "File hash" }
|
||||
required: [file]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: url
|
||||
name: virustotal-url
|
||||
description: "Reputation and analysis for a URL (base64-id resolved automatically)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
url: { type: string, description: "URL to look up" }
|
||||
required: [url]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Scans ───────────────────────────────────────────────────────────────────
|
||||
- id: file_rescan
|
||||
name: virustotal-file-rescan
|
||||
description: "Re-analyse an already-submitted file by hash. Use analysis_get to fetch results."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
file: { type: string, description: "File hash to rescan" }
|
||||
required: [file]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: url_scan
|
||||
name: virustotal-url-scan
|
||||
description: "Submit a URL for scanning. Use analysis_get to fetch results."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
url: { type: string, description: "URL to scan" }
|
||||
required: [url]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: analysis_get
|
||||
name: virustotal-analysis-get
|
||||
description: "Get the result of an analysis (from file_rescan or url_scan) by ID."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
id: { type: string, description: "Analysis ID" }
|
||||
required: [id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Intelligence / relationships ──────────────────────────────────────────
|
||||
- id: search
|
||||
name: virustotal-search
|
||||
description: "Search VirusTotal for a file hash, URL, domain, IP, or tag."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
query: { type: string, description: "Search query" }
|
||||
limit: { type: number, description: "Maximum number of results (default 10)" }
|
||||
required: [query]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: file_sandbox_report
|
||||
name: virustotal-file-sandbox-report
|
||||
description: "Retrieve sandbox behaviour reports for a file hash."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
file: { type: string, description: "File hash" }
|
||||
limit: { type: number, description: "Maximum number of reports (default 10)" }
|
||||
required: [file]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: passive_dns
|
||||
name: virustotal-passive-dns-data
|
||||
description: "Passive DNS resolutions for an IP address or a domain."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
resource: { type: string, description: "IP address or domain" }
|
||||
limit: { type: number, description: "Maximum number of resolutions (default 10)" }
|
||||
required: [resource]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Comments ──────────────────────────────────────────────────────────────
|
||||
- id: comments_get
|
||||
name: virustotal-comments-get
|
||||
description: "Get comments for an IP, domain, URL or file."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
resource: { type: string, description: "The IP, domain, URL or file hash" }
|
||||
resource_type: { type: string, description: "Resource type: ip, domain, url or file (auto-detected if omitted)" }
|
||||
limit: { type: number, description: "Maximum number of comments (default 10)" }
|
||||
required: [resource]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: comments_add
|
||||
name: virustotal-comments-add
|
||||
description: "Add a comment to an IP, domain, URL or file."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
resource: { type: string, description: "The IP, domain, URL or file hash" }
|
||||
resource_type: { type: string, description: "Resource type: ip, domain, url or file (auto-detected if omitted)" }
|
||||
comment: { type: string, description: "Comment text" }
|
||||
required: [resource, comment]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: comments_get_by_id
|
||||
name: virustotal-comments-get-by-id
|
||||
description: "Get a single comment by its ID."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
id: { type: string, description: "Comment ID" }
|
||||
required: [id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: comments_delete
|
||||
name: virustotal-comments-delete
|
||||
description: "Delete a comment by its ID."
|
||||
risk: destructive
|
||||
inputs_schema:
|
||||
properties:
|
||||
id: { type: string, description: "Comment ID" }
|
||||
required: [id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Connectivity test ─────────────────────────────────────────────────────
|
||||
- id: test_connection
|
||||
name: virustotal-test-connection
|
||||
description: "Verify connectivity and credentials (used by the Test button)."
|
||||
@@ -68,9 +186,4 @@ commands:
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema:
|
||||
properties: {}
|
||||
request:
|
||||
method: GET
|
||||
path: /ip_addresses/8.8.8.8
|
||||
auth_ref: apikey
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path):
|
||||
base, headers = _cfg()
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
analysis_id = urllib.parse.quote(str(_inputs().get("id", "")), safe="")
|
||||
print(json.dumps(request("analyses/" + analysis_id)))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,65 @@
|
||||
import json, os, sys, base64, ipaddress, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _b64url(u):
|
||||
return base64.urlsafe_b64encode(u.encode()).decode().strip("=")
|
||||
|
||||
|
||||
def _resource_path(resource, rtype):
|
||||
rtype = str(rtype or "").strip().lower()
|
||||
if not rtype:
|
||||
try:
|
||||
ipaddress.ip_address(resource)
|
||||
rtype = "ip"
|
||||
except ValueError:
|
||||
h = resource.strip()
|
||||
if len(h) in (32, 40, 64) and all(c in "0123456789abcdefABCDEF" for c in h):
|
||||
rtype = "file"
|
||||
elif "://" in resource:
|
||||
rtype = "url"
|
||||
else:
|
||||
rtype = "domain"
|
||||
coll = {"ip": "ip_addresses", "domain": "domains", "file": "files", "hash": "files", "url": "urls"}.get(rtype, "files")
|
||||
rid = _b64url(resource) if coll == "urls" else urllib.parse.quote(resource, safe="")
|
||||
return coll + "/" + rid
|
||||
|
||||
|
||||
def request(method, path, body=None):
|
||||
base, headers = _cfg()
|
||||
h = dict(headers)
|
||||
data = None
|
||||
if body is not None:
|
||||
data = json.dumps(body).encode("utf-8")
|
||||
h["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), data=data, headers=h, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
inp = _inputs()
|
||||
path = _resource_path(str(inp.get("resource", "")), inp.get("resource_type"))
|
||||
body = {"data": {"type": "comment", "attributes": {"text": inp.get("comment", "")}}}
|
||||
print(json.dumps(request("POST", path + "/comments", body=body)))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,35 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path):
|
||||
base, headers = _cfg()
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
return r.read().decode("utf-8", "replace")
|
||||
|
||||
|
||||
def run():
|
||||
comment_id = str(_inputs().get("id", ""))
|
||||
request("DELETE", "comments/" + urllib.parse.quote(comment_id, safe=""))
|
||||
print(json.dumps({"deleted": True, "id": comment_id}))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,69 @@
|
||||
import json, os, sys, base64, ipaddress, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _b64url(u):
|
||||
return base64.urlsafe_b64encode(u.encode()).decode().strip("=")
|
||||
|
||||
|
||||
def _resource_path(resource, rtype):
|
||||
rtype = str(rtype or "").strip().lower()
|
||||
if not rtype:
|
||||
try:
|
||||
ipaddress.ip_address(resource)
|
||||
rtype = "ip"
|
||||
except ValueError:
|
||||
h = resource.strip()
|
||||
if len(h) in (32, 40, 64) and all(c in "0123456789abcdefABCDEF" for c in h):
|
||||
rtype = "file"
|
||||
elif "://" in resource:
|
||||
rtype = "url"
|
||||
else:
|
||||
rtype = "domain"
|
||||
coll = {"ip": "ip_addresses", "domain": "domains", "file": "files", "hash": "files", "url": "urls"}.get(rtype, "files")
|
||||
rid = _b64url(resource) if coll == "urls" else urllib.parse.quote(resource, safe="")
|
||||
return coll + "/" + rid
|
||||
|
||||
|
||||
def request(method, path, params=None, body=None):
|
||||
base, headers = _cfg()
|
||||
url = base + "/" + path.lstrip("/")
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean, doseq=True)
|
||||
h = dict(headers)
|
||||
data = None
|
||||
if body is not None:
|
||||
data = json.dumps(body).encode("utf-8")
|
||||
h["Content-Type"] = "application/json"
|
||||
req = urllib.request.Request(url, data=data, headers=h, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
inp = _inputs()
|
||||
path = _resource_path(str(inp.get("resource", "")), inp.get("resource_type"))
|
||||
print(json.dumps(request("GET", path + "/comments", params={"limit": int(inp.get("limit") or 10)})))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,35 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path):
|
||||
base, headers = _cfg()
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
comment_id = urllib.parse.quote(str(_inputs().get("id", "")), safe="")
|
||||
print(json.dumps(request("comments/" + comment_id)))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,35 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path):
|
||||
base, headers = _cfg()
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
file_hash = urllib.parse.quote(str(_inputs().get("file", "")), safe="")
|
||||
print(json.dumps(request("files/" + file_hash)))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,35 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path):
|
||||
base, headers = _cfg()
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
file_hash = urllib.parse.quote(str(_inputs().get("file", "")), safe="")
|
||||
print(json.dumps(request("POST", "files/" + file_hash + "/analyse")))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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 _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path, params):
|
||||
base, headers = _cfg()
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
url = base + "/" + path.lstrip("/") + ("?" + urllib.parse.urlencode(clean, doseq=True) if clean else "")
|
||||
req = urllib.request.Request(url, headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
inp = _inputs()
|
||||
file_hash = urllib.parse.quote(str(inp.get("file", "")), safe="")
|
||||
print(json.dumps(request("files/" + file_hash + "/behaviours", {"limit": int(inp.get("limit") or 10)})))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,35 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path):
|
||||
base, headers = _cfg()
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
domain = urllib.parse.quote(str(_inputs().get("domain", "")), safe="")
|
||||
print(json.dumps(request("domains/" + domain)))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,35 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path):
|
||||
base, headers = _cfg()
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
ip = urllib.parse.quote(str(_inputs().get("ip", "")), safe="")
|
||||
print(json.dumps(request("ip_addresses/" + ip)))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,44 @@
|
||||
import json, os, sys, ipaddress, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path, params):
|
||||
base, headers = _cfg()
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
url = base + "/" + path.lstrip("/") + ("?" + urllib.parse.urlencode(clean, doseq=True) if clean else "")
|
||||
req = urllib.request.Request(url, headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
inp = _inputs()
|
||||
resource = str(inp.get("resource", ""))
|
||||
try:
|
||||
ipaddress.ip_address(resource)
|
||||
collection = "ip_addresses"
|
||||
except ValueError:
|
||||
collection = "domains"
|
||||
rid = urllib.parse.quote(resource, safe="")
|
||||
print(json.dumps(request(collection + "/" + rid + "/resolutions", {"limit": int(inp.get("limit") or 10)})))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,37 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(path, params):
|
||||
base, headers = _cfg()
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
url = base + "/" + path.lstrip("/") + ("?" + urllib.parse.urlencode(clean, doseq=True) if clean else "")
|
||||
req = urllib.request.Request(url, headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
inp = _inputs()
|
||||
print(json.dumps(request("search", {"query": inp.get("query"), "limit": int(inp.get("limit") or 10)})))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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 _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def request(path):
|
||||
base, headers = _cfg()
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
request("ip_addresses/8.8.8.8")
|
||||
print(json.dumps({"ok": True}))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
except urllib.error.HTTPError as e:
|
||||
detail = e.read().decode("utf-8", "replace")
|
||||
msg = "API key is not valid." if e.code in (401, 403) else "HTTP " + str(e.code)
|
||||
print(json.dumps({"ok": False, "error": msg, "detail": detail}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"ok": False, "error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,39 @@
|
||||
import json, os, sys, base64, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _b64url(u):
|
||||
return base64.urlsafe_b64encode(u.encode()).decode().strip("=")
|
||||
|
||||
|
||||
def request(path):
|
||||
base, headers = _cfg()
|
||||
req = urllib.request.Request(base + "/" + path.lstrip("/"), headers=headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
url_value = str(_inputs().get("url", ""))
|
||||
print(json.dumps(request("urls/" + _b64url(url_value))))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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,35 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
base = str(s.get("base_url") or "https://www.virustotal.com/api/v3").rstrip("/")
|
||||
headers = {"x-apikey": s.get("api_key", ""), "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def run():
|
||||
base, headers = _cfg()
|
||||
url_value = str(_inputs().get("url", ""))
|
||||
# VirusTotal expects the URL submission as form-encoded data.
|
||||
data = urllib.parse.urlencode({"url": url_value}).encode("utf-8")
|
||||
h = dict(headers)
|
||||
h["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
req = urllib.request.Request(base + "/urls", data=data, headers=h, method="POST")
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
print(json.dumps(json.loads(raw) if raw else {}))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
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