feat(rapid7-insightidr): InsightIDR integration (19 commands + OCSF ingestion)
REST API integration for Rapid7 InsightIDR. Investigation ingestion (list_investigations) with an exhaustive OCSF mapper and a bundled default incident type, plus 18 commands across investigations (list/get/search/ create/update/assign/set-status/bulk-close), investigation alerts and Rapid7 product alerts, custom threat indicators (add/replace), log management and LEQL log/log-set queries with downloads, and user directory search. API v1/v2 selectable per instance (is_v2) and per command (api_version); multi-customer query parameter supported on v2 calls. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
name: "Rapid7 InsightIDR Investigation"
|
||||
color: "#ff5630"
|
||||
icon: "alert"
|
||||
@@ -0,0 +1,331 @@
|
||||
id: rapid7_insightidr
|
||||
name: Rapid7 InsightIDR
|
||||
version: 1.0.0
|
||||
description: "Rapid7 InsightIDR (REST API) — investigation ingestion and full lifecycle (list/get/search/create/update/assign/set-status/bulk-close), investigation alerts and Rapid7 product alerts, custom threat indicators (add/replace), log management and LEQL log/log-set queries with downloads, and user directory search."
|
||||
changelog: "1.0.0 — Initial release: investigation ingestion (list_investigations) with an exhaustive OCSF mapper, 18 commands across investigations, alerts, custom threats, logs/LEQL search and users. API v1/v2 selectable per instance and per command."
|
||||
category: siem
|
||||
|
||||
# Per-instance configuration. The Insight platform region selects the API host
|
||||
# (https://<region>.api.insight.rapid7.com). Authentication is an Organization
|
||||
# API key sent in the X-Api-Key header.
|
||||
config_schema:
|
||||
properties:
|
||||
region:
|
||||
type: string
|
||||
description: "Insight platform region: us, eu, ca, au or ap"
|
||||
default: us
|
||||
api_key:
|
||||
type: string
|
||||
description: "InsightIDR Organization API key (Read/Write)"
|
||||
x-soar-sensitive: true
|
||||
is_v2:
|
||||
type: string
|
||||
description: "Use API v2 by default for investigation commands (true/false). Can be overridden per command with api_version. Defaults to true (v2)."
|
||||
default: "true"
|
||||
is_multi_customer:
|
||||
type: string
|
||||
description: "Set to true if the API key has multi-customer access (adds the multi-customer query parameter on v2 calls)."
|
||||
default: "false"
|
||||
required:
|
||||
- region
|
||||
- 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_investigations
|
||||
name: rapid7-insight-idr-list-investigations
|
||||
description: "List investigations, sorted by created_time descending. Used for ingestion: results path = data. Investigations aggregate the applicable alert data and are tied to alerts and detection rules."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
api_version: { type: string, description: "API version to use: V1, V2 or Default (uses the instance default)" }
|
||||
statuses: { type: string, description: "Comma-separated statuses to include (open, investigating, closed)" }
|
||||
start_time: { type: string, description: "Only investigations created after this ISO-8601 timestamp (e.g. 2018-07-01T00:00:00Z). Incremental fetch watermark; V2 only." }
|
||||
end_time: { type: string, description: "Only investigations created before this ISO-8601 timestamp. V2 only." }
|
||||
time_range: { type: string, description: "Relative time range string (e.g. 1 week, 1 day) instead of start_time/end_time" }
|
||||
sources: { type: string, description: "Comma-separated sources to include (User, Alert). V2 only." }
|
||||
priorities: { type: string, description: "Comma-separated priorities to include (Unspecified, Low, Medium, High, Critical). V2 only." }
|
||||
assignee_email: { type: string, description: "Only investigations assigned to this user email" }
|
||||
tags: { type: string, description: "Comma-separated tags; only investigations having all of them are returned. V2 only." }
|
||||
sort_field: { type: string, description: "Field to sort by (Created time, Priority, RRN Last Created Alert, Last Detection Alert). V2 only." }
|
||||
sort_direction: { type: string, description: "Sort direction (ASC, DESC). V2 only." }
|
||||
index: { type: string, description: "0-based page index" }
|
||||
page_size: { type: string, description: "Page size (1-1000)" }
|
||||
limit: { type: number, description: "Maximum number of records to retrieve (default 50)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
ingest:
|
||||
results_path: data
|
||||
dedup_key: rrn
|
||||
incremental_field: start_time
|
||||
|
||||
- id: get_investigation
|
||||
name: rapid7-insight-idr-get-investigation
|
||||
description: "Get a single investigation by ID or Rapid7 Resource Name (RRN). With api_version=V2 the ID must be in RRN format."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
api_version: { type: string, description: "API version to use: V1, V2 or Default" }
|
||||
investigation_id: { type: string, description: "Investigation ID or RRN" }
|
||||
required: [investigation_id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: search_investigation
|
||||
name: rapid7-insight-idr-search-investigation
|
||||
description: "Search investigations matching the given search/sort criteria (v2)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
start_time: { type: string, description: "ISO-8601 lower bound on created_time (default 28 days ago)" }
|
||||
end_time: { type: string, description: "ISO-8601 upper bound on created_time (default now)" }
|
||||
actor_asset_hostname: { type: string, description: "Comma-separated values; CONTAINS match on actor_asset_hostname" }
|
||||
actor_user_name: { type: string, description: "Comma-separated values; CONTAINS match on actor_user_name" }
|
||||
alert_mitre_t_codes: { type: string, description: "Comma-separated values; EQUALS match on alert_mitre_t_codes" }
|
||||
alert_rule_rrn: { type: string, description: "Comma-separated values; EQUALS match on alert_rule_rrn" }
|
||||
assignee_id: { type: string, description: "Comma-separated values; EQUALS match on assignee_id" }
|
||||
organization_id: { type: string, description: "Comma-separated values; EQUALS match on organization_id" }
|
||||
priority: { type: string, description: "Comma-separated values; EQUALS match on priority (UNSPECIFIED, LOW, MEDIUM, HIGH, CRITICAL)" }
|
||||
rrn: { type: string, description: "Comma-separated values; EQUALS match on rrn" }
|
||||
source: { type: string, description: "Comma-separated values; EQUALS match on source (USER, ALERT)" }
|
||||
status: { type: string, description: "Comma-separated values; EQUALS match on status (OPEN, CLOSED, INVESTIGATING, WAITING)" }
|
||||
title: { type: string, description: "Comma-separated values; CONTAINS match on title" }
|
||||
sort: { type: string, description: "Comma-separated fields to sort by (Created time, Priority, RRN, Alert created time, Alert detection created time)" }
|
||||
sort_direction: { type: string, description: "Sort direction (asc, desc)" }
|
||||
index: { type: string, description: "0-based page index" }
|
||||
page_size: { type: string, description: "Page size (1-1000)" }
|
||||
limit: { type: number, description: "Maximum number of records to retrieve (default 50)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: create_investigation
|
||||
name: rapid7-insight-idr-create-investigation
|
||||
description: "Create a new investigation manually (v2)."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
title: { type: string, description: "Name of the investigation" }
|
||||
status: { type: string, description: "Open, Investigating or Closed (default Open)" }
|
||||
priority: { type: string, description: "Unspecified, Low, Medium, High or Critical (default Unspecified)" }
|
||||
disposition: { type: string, description: "Undecided, Benign, Malicious or Not Applicable (default Undecided)" }
|
||||
user_email_address: { type: string, description: "Email of the user to assign the investigation to" }
|
||||
required: [title]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: update_investigation
|
||||
name: rapid7-insight-idr-update-investigation
|
||||
description: "Update fields of an investigation by ID or RRN (v2)."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
investigation_id: { type: string, description: "Investigation ID or RRN to update" }
|
||||
title: { type: string, description: "Name of the investigation" }
|
||||
status: { type: string, description: "Open, Investigating or Closed" }
|
||||
priority: { type: string, description: "Unspecified, Low, Medium, High or Critical" }
|
||||
disposition: { type: string, description: "Undecided, Benign, Malicious or Not Applicable" }
|
||||
user_email_address: { type: string, description: "Email of the user to assign the investigation to" }
|
||||
threat_command_free_text: { type: string, description: "Additional text when closing an associated Threat Command alert (status=Closed)" }
|
||||
threat_command_close_reason: { type: string, description: "Threat Command close reason (status=Closed)" }
|
||||
required: [investigation_id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: assign_user
|
||||
name: rapid7-insight-idr-assign-user
|
||||
description: "Assign a user (by email) to one or more investigations. With api_version=V2 the IDs must be in RRN format."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
api_version: { type: string, description: "API version to use: V1, V2 or Default" }
|
||||
investigation_id: { type: string, description: "Comma-separated investigation IDs or RRNs" }
|
||||
user_email_address: { type: string, description: "Email of the user to assign" }
|
||||
required: [investigation_id, user_email_address]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: set_status
|
||||
name: rapid7-insight-idr-set-status
|
||||
description: "Set the status of one or more investigations. Closing requires a disposition (v2). With api_version=V2 the IDs must be in RRN format."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
api_version: { type: string, description: "API version to use: V1, V2 or Default" }
|
||||
investigation_id: { type: string, description: "Comma-separated investigation IDs or RRNs" }
|
||||
status: { type: string, description: "open, closed, investigating or waiting" }
|
||||
disposition: { type: string, description: "benign, malicious or not_applicable (status=closed, V2 only)" }
|
||||
threat_command_close_reason: { type: string, description: "Threat Command close reason (status=closed, V2 only)" }
|
||||
threat_command_free_text: { type: string, description: "Additional text for a Threat Command alert (status=closed, V2 only)" }
|
||||
required: [investigation_id, status]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: close_investigations
|
||||
name: rapid7-insight-idr-close-investigations
|
||||
description: "Bulk-close all investigations matching the request parameters within a time window (v2)."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
source: { type: string, description: "Investigation source to close: ALERT, MANUAL or HUNT. ALERT requires alert_type or detection_rule_rrn." }
|
||||
start_time: { type: string, description: "ISO-8601 lower bound on createTime (e.g. 2018-07-01T00:00:00Z)" }
|
||||
end_time: { type: string, description: "ISO-8601 upper bound on createTime (e.g. 2018-07-28T23:59:00Z)" }
|
||||
alert_type: { type: string, description: "Alert category to close. Required when source=ALERT." }
|
||||
disposition: { type: string, description: "Disposition to set: Undecided, Benign, Malicious or Not Applicable (default Not Applicable)" }
|
||||
detection_rule_rrn: { type: string, description: "Detection rule RRN; only investigations linked to it are closed. Requires alert_type 'Attacker Behavior Detected'." }
|
||||
max_investigations_to_close: { type: string, description: "Maximum number of investigations to close (no maximum if omitted)" }
|
||||
required: [source, start_time, end_time]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: list_investigation_alerts
|
||||
name: rapid7-insight-idr-list-investigation-alerts
|
||||
description: "List all alerts associated with an investigation, sorted by alert created time descending (v2)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
investigation_id: { type: string, description: "Investigation ID or RRN (V2 RRN format)" }
|
||||
all_results: { type: string, description: "Return all results, overriding the limit (true/false, default false)" }
|
||||
limit: { type: number, description: "Maximum number of records to retrieve (default 50)" }
|
||||
required: [investigation_id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: list_investigation_product_alerts
|
||||
name: rapid7-insight-idr-list-investigation-product-alerts
|
||||
description: "List all Rapid7 product alerts (from other Rapid7 products) associated with an investigation (v2)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
investigation_id: { type: string, description: "Investigation ID or RRN (V2 RRN format)" }
|
||||
all_results: { type: string, description: "Return all results, overriding the limit (true/false, default false)" }
|
||||
limit: { type: number, description: "Maximum number of records to retrieve (default 50)" }
|
||||
required: [investigation_id]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: add_threat_indicators
|
||||
name: rapid7-insight-idr-add-threat-indicators
|
||||
description: "Add indicators (IPs, hashes, domains, URLs) to one or more custom threats by key."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
key: { type: string, description: "Comma-separated threat keys to add indicators to" }
|
||||
ip_addresses: { type: string, description: "Comma-separated IP address indicators" }
|
||||
hashes: { type: string, description: "Comma-separated hash indicators" }
|
||||
domain_names: { type: string, description: "Comma-separated domain indicators" }
|
||||
url: { type: string, description: "Comma-separated URL indicators" }
|
||||
required: [key]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: replace_threat_indicators
|
||||
name: rapid7-insight-idr-replace-threat-indicators
|
||||
description: "Replace all indicators of one or more custom threats: deletes existing indicators and adds the supplied ones."
|
||||
risk: safe_write
|
||||
inputs_schema:
|
||||
properties:
|
||||
key: { type: string, description: "Comma-separated threat keys to replace indicators for" }
|
||||
ip_addresses: { type: string, description: "Comma-separated IP address indicators" }
|
||||
hashes: { type: string, description: "Comma-separated hash indicators" }
|
||||
domain_names: { type: string, description: "Comma-separated domain indicators" }
|
||||
url: { type: string, description: "Comma-separated URL indicators" }
|
||||
required: [key]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: list_logs
|
||||
name: rapid7-insight-idr-list-logs
|
||||
description: "List all logs available to the account."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: list_log_sets
|
||||
name: rapid7-insight-idr-list-log-sets
|
||||
description: "List all log sets configured for the InsightIDR instance."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: query_log
|
||||
name: rapid7-insight-idr-query-log
|
||||
description: "Run a LEQL query against a single log, following pagination links to gather all events."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
log_id: { type: string, description: "Log key to query" }
|
||||
query: { type: string, description: "Valid LEQL query (no calculations)" }
|
||||
time_range: { type: string, description: "Relative time range (e.g. 1 week, 1 day). When set, start_time/end_time are not needed." }
|
||||
start_time: { type: string, description: "Lower bound as a UNIX timestamp in milliseconds" }
|
||||
end_time: { type: string, description: "Upper bound as a UNIX timestamp in milliseconds" }
|
||||
logs_per_page: { type: string, description: "Maximum log entries per page (default 50)" }
|
||||
sequence_number: { type: string, description: "Earliest sequence number of a log entry to start from" }
|
||||
required: [log_id, query]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: query_log_set
|
||||
name: rapid7-insight-idr-query-log-set
|
||||
description: "Run a LEQL query against a log set, following pagination links to gather all events."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
log_set_id: { type: string, description: "Log set ID to query" }
|
||||
query: { type: string, description: "Valid LEQL query (no calculations)" }
|
||||
time_range: { type: string, description: "Relative time range (e.g. 1 week, 1 day). When set, start_time/end_time are not needed." }
|
||||
start_time: { type: string, description: "Lower bound as a UNIX timestamp in milliseconds" }
|
||||
end_time: { type: string, description: "Upper bound as a UNIX timestamp in milliseconds" }
|
||||
logs_per_page: { type: string, description: "Maximum log entries per page (default 50)" }
|
||||
sequence_number: { type: string, description: "Earliest sequence number of a log entry to start from" }
|
||||
required: [log_set_id, query]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: download_logs
|
||||
name: rapid7-insight-idr-download-logs
|
||||
description: "Download raw log events for up to 10 logs over a time window, optionally filtered by a LEQL query. Returns the events as text."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
log_ids: { type: string, description: "Comma-separated log IDs to download (up to 10)" }
|
||||
time_range: { type: string, description: "Relative time range (e.g. Last 4 Days). Defaults to Last 3 days when no bounds are given." }
|
||||
start_time: { type: string, description: "Lower bound as a UNIX timestamp in milliseconds" }
|
||||
end_time: { type: string, description: "Upper bound as a UNIX timestamp in milliseconds" }
|
||||
query: { type: string, description: "LEQL query to match desired events (no calculations)" }
|
||||
limit: { type: string, description: "Maximum number of events to download (cannot exceed 20 million)" }
|
||||
required: [log_ids]
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
- id: list_users
|
||||
name: rapid7-insight-idr-list-users
|
||||
description: "List users matching the search/sort criteria, or retrieve a single user by RRN."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties:
|
||||
rrn: { type: string, description: "RRN of a single user to retrieve. When set, the other filters are ignored." }
|
||||
first_name: { type: string, description: "Comma-separated values to match on first_name (requires search_operator)" }
|
||||
last_name: { type: string, description: "Comma-separated values to match on last_name (requires search_operator)" }
|
||||
name: { type: string, description: "Comma-separated values to match on name (requires search_operator)" }
|
||||
search_operator: { type: string, description: "Filter operator when first_name/last_name/name are used: contains or equals" }
|
||||
sort: { type: string, description: "Comma-separated fields to sort by (first_name, last_name, name)" }
|
||||
sort_direction: { type: string, description: "Sort direction (asc, desc)" }
|
||||
index: { type: string, description: "0-based page index" }
|
||||
page_size: { type: string, description: "Page size (1-1000)" }
|
||||
limit: { type: number, description: "Maximum number of records to retrieve (default 50)" }
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
# ── Connectivity test ─────────────────────────────────────────────────────
|
||||
- id: test_connection
|
||||
name: rapid7-insight-idr-test-connection
|
||||
description: "Verify connectivity and credentials (used by the Test button)."
|
||||
risk: read
|
||||
inputs_schema:
|
||||
properties: {}
|
||||
required: []
|
||||
outputs_schema: { properties: {} }
|
||||
|
||||
ingestion:
|
||||
command: list_investigations
|
||||
mapper: list_investigations
|
||||
default_incident_type: "Rapid7 InsightIDR Investigation"
|
||||
@@ -0,0 +1,33 @@
|
||||
name: "Rapid7 InsightIDR Investigations → OCSF"
|
||||
description: "Maps a Rapid7 InsightIDR investigation (idr/v2/investigations, results_path = data) to OCSF Incident Finding fields. Investigations aggregate the applicable alert data; use list_investigation_alerts for per-alert detail. v1 (id) and v2 (rrn) shapes are both covered via fallbacks."
|
||||
field_mappings:
|
||||
title: "title"
|
||||
# toSeverity maps CRITICAL→5, HIGH→3, MEDIUM→2, LOW→1; UNSPECIFIED falls back to the rule default.
|
||||
severity: "priority"
|
||||
# How the investigation was generated (USER / ALERT).
|
||||
source: "source"
|
||||
# results_path = data; source_path is JSONata over ONE investigation object.
|
||||
# Paths absent from a given investigation return nothing and are skipped, so v1/v2 fallbacks are safe.
|
||||
ocsf:
|
||||
# ── Finding ───────────────────────────────────────────────────────
|
||||
- { source_path: "rrn ? rrn : id", ocsf_field: "finding_info.uid" }
|
||||
- { source_path: "title", ocsf_field: "finding_info.title" }
|
||||
- { source_path: "created_time", ocsf_field: "finding_info.created_time" }
|
||||
- { source_path: "last_accessed", ocsf_field: "finding_info.modified_time" }
|
||||
- { source_path: "first_alert_time ? first_alert_time : alert.first_event_time", ocsf_field: "finding_info.first_seen_time" }
|
||||
- { source_path: "latest_alert_time", ocsf_field: "finding_info.last_seen_time" }
|
||||
- { source_path: "tags", ocsf_field: "metadata.labels" }
|
||||
# ── Incident state ────────────────────────────────────────────────
|
||||
- { source_path: "status", ocsf_field: "status" }
|
||||
- { source_path: "priority", ocsf_field: "priority" }
|
||||
- { source_path: "disposition", ocsf_field: "disposition" }
|
||||
- { source_path: "source", ocsf_field: "activity_name" }
|
||||
- { source_path: "responsibility", ocsf_field: "comment" }
|
||||
# ── Assignee (User) ───────────────────────────────────────────────
|
||||
- { source_path: "assignee.name ? assignee.name : assignee_name", ocsf_field: "assignee.name" }
|
||||
- { source_path: "assignee.email ? assignee.email : assignee_email", ocsf_field: "assignee.email_addr" }
|
||||
# ── Owning organization ───────────────────────────────────────────
|
||||
- { source_path: "organization_id", ocsf_field: "cloud.org.uid" }
|
||||
# ── Originating alert (v1 shape) ──────────────────────────────────
|
||||
- { source_path: "alert.type", ocsf_field: "finding_info.analytic.name" }
|
||||
- { source_path: "alert.type_description", ocsf_field: "finding_info.analytic.desc" }
|
||||
@@ -0,0 +1,73 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _list(v):
|
||||
if v in (None, ""):
|
||||
return []
|
||||
if isinstance(v, list):
|
||||
return [str(x).strip() for x in v if str(x).strip()]
|
||||
return [p.strip() for p in str(v).split(",") if p.strip()]
|
||||
|
||||
|
||||
def _prune(d):
|
||||
return {k: v for k, v in d.items() if v}
|
||||
|
||||
|
||||
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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
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=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
# Endpoint suffix differs between add and replace; this script adds.
|
||||
ACTION = "add"
|
||||
|
||||
|
||||
def run():
|
||||
inp = _inputs()
|
||||
body = _prune({
|
||||
"ips": _list(inp.get("ip_addresses")),
|
||||
"hashes": _list(inp.get("hashes")),
|
||||
"domain_names": _list(inp.get("domain_names")),
|
||||
"urls": _list(inp.get("url")),
|
||||
})
|
||||
results = []
|
||||
for key in _list(inp.get("key")):
|
||||
out = request(
|
||||
"POST",
|
||||
"idr/v1/customthreats/key/" + urllib.parse.quote(key, safe="") + "/indicators/" + ACTION,
|
||||
params={"format": "json"},
|
||||
body=body,
|
||||
)
|
||||
results.append(out.get("threat", out))
|
||||
print(json.dumps({"data": results}))
|
||||
|
||||
|
||||
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, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
multi = str(s.get("is_multi_customer") or "").strip().lower() in ("1", "true", "yes")
|
||||
is_v2 = str(s.get("is_v2") or "true").strip().lower() in ("1", "true", "yes")
|
||||
return base, headers, multi, is_v2
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _api_version(inp, is_v2):
|
||||
v = str(inp.get("api_version") or "Default").strip()
|
||||
return v if v in ("V1", "V2") else ("V2" if is_v2 else "V1")
|
||||
|
||||
|
||||
def _list(v):
|
||||
if v in (None, ""):
|
||||
return []
|
||||
if isinstance(v, list):
|
||||
return [str(x).strip() for x in v if str(x).strip()]
|
||||
return [p.strip() for p in str(v).split(",") if p.strip()]
|
||||
|
||||
|
||||
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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
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=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
_, _, multi, is_v2 = _cfg()
|
||||
inp = _inputs()
|
||||
api_version = _api_version(inp, is_v2)
|
||||
email = inp.get("user_email_address")
|
||||
params = {"multi-customer": "true" if (multi and api_version == "V2") else None}
|
||||
results = []
|
||||
for inv_id in _list(inp.get("investigation_id")):
|
||||
results.append(request(
|
||||
"PUT",
|
||||
"idr/" + api_version.lower() + "/investigations/" + urllib.parse.quote(inv_id, safe="") + "/assignee",
|
||||
params=params,
|
||||
body={"user_email_address": email},
|
||||
))
|
||||
print(json.dumps({"data": results}))
|
||||
|
||||
|
||||
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,54 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _prune(d):
|
||||
return {k: v for k, v in d.items() if v not in (None, "", {}, [])}
|
||||
|
||||
|
||||
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():
|
||||
inp = _inputs()
|
||||
disposition = inp.get("disposition") or "Not Applicable"
|
||||
max_close = inp.get("max_investigations_to_close")
|
||||
body = _prune({
|
||||
"source": inp.get("source"),
|
||||
"alert_type": inp.get("alert_type"),
|
||||
"disposition": disposition.replace(" ", "_"),
|
||||
"detection_rule_rrn": inp.get("detection_rule_rrn"),
|
||||
"from": inp.get("start_time"),
|
||||
"to": inp.get("end_time"),
|
||||
"max_investigations_to_close": int(max_close) if max_close not in (None, "") else None,
|
||||
})
|
||||
out = request("POST", "idr/v2/investigations/bulk_close", body=body)
|
||||
ids = out.get("ids", [])
|
||||
print(json.dumps({"ids": ids, "data": [{"id": i, "status": "CLOSED"} for i in ids]}))
|
||||
|
||||
|
||||
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,49 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _prune(d):
|
||||
return {k: v for k, v in d.items() if v not in (None, "", {}, [])}
|
||||
|
||||
|
||||
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():
|
||||
inp = _inputs()
|
||||
disposition = str(inp.get("disposition") or "Undecided").replace(" ", "_")
|
||||
body = _prune({
|
||||
"assignee": _prune({"email": inp.get("user_email_address")}),
|
||||
"disposition": disposition,
|
||||
"priority": inp.get("priority") or "Unspecified",
|
||||
"status": inp.get("status") or "Open",
|
||||
"title": inp.get("title"),
|
||||
})
|
||||
print(json.dumps(request("POST", "idr/v2/investigations", 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,52 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": 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()
|
||||
inp = _inputs()
|
||||
start_time = inp.get("start_time")
|
||||
end_time = inp.get("end_time")
|
||||
time_range = inp.get("time_range")
|
||||
if not (start_time or end_time or time_range):
|
||||
time_range = "Last 3 days"
|
||||
params = {
|
||||
"from": start_time,
|
||||
"to": end_time,
|
||||
"time_range": time_range,
|
||||
"query": inp.get("query"),
|
||||
"limit": inp.get("limit"),
|
||||
}
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
# Up to 10 log IDs are joined with ':' in the path.
|
||||
log_ids = str(inp.get("log_ids", "")).replace(",", ":")
|
||||
url = base + "log_search/download/logs/" + urllib.parse.quote(log_ids, safe=":")
|
||||
if clean:
|
||||
url += "?" + urllib.parse.urlencode(clean, doseq=True)
|
||||
dl_headers = dict(headers)
|
||||
dl_headers["Accept-Encoding"] = ""
|
||||
req = urllib.request.Request(url, headers=dl_headers, method="GET")
|
||||
with urllib.request.urlopen(req, timeout=120) as r:
|
||||
content = r.read().decode("utf-8", "replace")
|
||||
print(json.dumps({"content": content, "log_ids": inp.get("log_ids")}))
|
||||
|
||||
|
||||
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,60 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
multi = str(s.get("is_multi_customer") or "").strip().lower() in ("1", "true", "yes")
|
||||
is_v2 = str(s.get("is_v2") or "true").strip().lower() in ("1", "true", "yes")
|
||||
return base, headers, multi, is_v2
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _api_version(inp, is_v2):
|
||||
v = str(inp.get("api_version") or "Default").strip()
|
||||
return v if v in ("V1", "V2") else ("V2" if is_v2 else "V1")
|
||||
|
||||
|
||||
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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
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=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
_, _, multi, is_v2 = _cfg()
|
||||
inp = _inputs()
|
||||
api_version = _api_version(inp, is_v2)
|
||||
inv_id = inp.get("investigation_id", "")
|
||||
if api_version == "V2":
|
||||
out = request("GET", "idr/v2/investigations/" + urllib.parse.quote(inv_id, safe=""),
|
||||
params={"multi-customer": "true" if multi else None})
|
||||
print(json.dumps(out if out.get("rrn") else {}))
|
||||
return
|
||||
# V1 has no get-by-id endpoint: list and match on id.
|
||||
data = request("GET", "idr/v1/investigations", params={"size": 1000}).get("data", [])
|
||||
match = next((i for i in data if i.get("id") == inv_id), {})
|
||||
print(json.dumps(match))
|
||||
|
||||
|
||||
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,51 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
multi = str(s.get("is_multi_customer") or "").strip().lower() in ("1", "true", "yes")
|
||||
return base, headers, multi
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, params=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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
req = urllib.request.Request(url, 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():
|
||||
_, _, multi = _cfg()
|
||||
inp = _inputs()
|
||||
inv_id = inp.get("investigation_id", "")
|
||||
out = request("GET", "idr/v2/investigations/" + urllib.parse.quote(inv_id, safe="") + "/alerts",
|
||||
params={"multi-customer": "true" if multi else None})
|
||||
data = out.get("data", [])
|
||||
all_results = str(inp.get("all_results") or "false").strip().lower() in ("1", "true", "yes")
|
||||
if not all_results:
|
||||
limit = int(inp.get("limit") or 50)
|
||||
data = data[:limit]
|
||||
print(json.dumps({"rrn": inv_id, "alert": data}))
|
||||
|
||||
|
||||
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, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
multi = str(s.get("is_multi_customer") or "").strip().lower() in ("1", "true", "yes")
|
||||
return base, headers, multi
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def request(method, path, params=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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
req = urllib.request.Request(url, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _flatten(response):
|
||||
# Each entry holds a product 'type' plus one or more product alert lists/dicts.
|
||||
rows = []
|
||||
for result in response or []:
|
||||
for product_name in list(result.keys()):
|
||||
value = result[product_name]
|
||||
if isinstance(value, list):
|
||||
for alert in value:
|
||||
rows.append(dict(alert, name=result.get("type")))
|
||||
elif isinstance(value, dict):
|
||||
rows.append(dict(value, name=result.get("type")))
|
||||
return rows
|
||||
|
||||
|
||||
def run():
|
||||
_, _, multi = _cfg()
|
||||
inp = _inputs()
|
||||
inv_id = inp.get("investigation_id", "")
|
||||
out = request("GET", "idr/v2/investigations/" + urllib.parse.quote(inv_id, safe="") + "/rapid7-product-alerts",
|
||||
params={"multi-customer": "true" if multi else None})
|
||||
data = _flatten(out if isinstance(out, list) else out.get("data", []))
|
||||
all_results = str(inp.get("all_results") or "false").strip().lower() in ("1", "true", "yes")
|
||||
if not all_results:
|
||||
limit = int(inp.get("limit") or 50)
|
||||
data = data[:limit]
|
||||
print(json.dumps({"rrn": inv_id, "ProductAlert": data}))
|
||||
|
||||
|
||||
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,93 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
ISO = "%Y-%m-%dT%H:%M:%SZ"
|
||||
_UNITS = {"second": 1, "minute": 60, "hour": 3600, "day": 86400, "week": 604800, "month": 2592000, "year": 31536000}
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
multi = str(s.get("is_multi_customer") or "").strip().lower() in ("1", "true", "yes")
|
||||
is_v2 = str(s.get("is_v2") or "true").strip().lower() in ("1", "true", "yes")
|
||||
return base, headers, multi, is_v2
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _api_version(inp, is_v2):
|
||||
v = str(inp.get("api_version") or "Default").strip()
|
||||
return v if v in ("V1", "V2") else ("V2" if is_v2 else "V1")
|
||||
|
||||
|
||||
def _rel_seconds(text):
|
||||
if not text:
|
||||
return None
|
||||
num = unit = None
|
||||
for t in str(text).lower().replace("last", "").split():
|
||||
if t.isdigit():
|
||||
num = int(t)
|
||||
elif t.rstrip("s") in _UNITS:
|
||||
unit = t.rstrip("s")
|
||||
return num * _UNITS[unit] if (num is not None and unit) else None
|
||||
|
||||
|
||||
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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
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=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
_, _, multi, is_v2 = _cfg()
|
||||
inp = _inputs()
|
||||
api_version = _api_version(inp, is_v2)
|
||||
limit = inp.get("limit") or 50
|
||||
start_time = inp.get("start_time")
|
||||
end_time = inp.get("end_time")
|
||||
secs = _rel_seconds(inp.get("time_range"))
|
||||
if secs:
|
||||
now = datetime.now(timezone.utc)
|
||||
start_time = (now - timedelta(seconds=secs)).strftime(ISO)
|
||||
end_time = now.strftime(ISO)
|
||||
params = {
|
||||
"index": inp.get("index") or "0",
|
||||
"size": inp.get("page_size") or limit,
|
||||
"statuses": inp.get("statuses"),
|
||||
"start_time": start_time,
|
||||
"end_time": end_time,
|
||||
}
|
||||
if api_version == "V2":
|
||||
params.update({
|
||||
"sources": inp.get("sources"),
|
||||
"priorities": inp.get("priorities"),
|
||||
"assignee_email": inp.get("assignee_email"),
|
||||
"sort_field": inp.get("sort_field"),
|
||||
"sort_direction": inp.get("sort_direction"),
|
||||
"tags": inp.get("tags"),
|
||||
"multi-customer": "true" if multi else None,
|
||||
})
|
||||
endpoint = "idr/" + api_version.lower() + "/investigations"
|
||||
print(json.dumps(request("GET", endpoint, params=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,31 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
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():
|
||||
print(json.dumps(request("GET", "log_search/management/logsets")))
|
||||
|
||||
|
||||
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,31 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
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():
|
||||
print(json.dumps(request("GET", "log_search/management/logs")))
|
||||
|
||||
|
||||
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,72 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
USER_SEARCH = ["first_name", "last_name", "name"]
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _list(v):
|
||||
if v in (None, ""):
|
||||
return []
|
||||
if isinstance(v, list):
|
||||
return [str(x).strip() for x in v if str(x).strip()]
|
||||
return [p.strip() for p in str(v).split(",") if p.strip()]
|
||||
|
||||
|
||||
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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
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=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
inp = _inputs()
|
||||
rrn = inp.get("rrn")
|
||||
if rrn:
|
||||
print(json.dumps(request("GET", "idr/v1/users/" + urllib.parse.quote(rrn, safe=""))))
|
||||
return
|
||||
operator = inp.get("search_operator")
|
||||
search = []
|
||||
for field in USER_SEARCH:
|
||||
values = _list(inp.get(field))
|
||||
if values and not operator:
|
||||
raise ValueError("search_operator is required to use first_name/last_name/name filters.")
|
||||
for value in values:
|
||||
search.append({"field": field, "operator": str(operator).upper(), "value": value})
|
||||
direction = str(inp.get("sort_direction") or "asc").upper()
|
||||
sort = [{"field": f, "order": direction} for f in _list(inp.get("sort"))]
|
||||
params = {"index": inp.get("index") or "0", "size": inp.get("page_size") or inp.get("limit") or 50}
|
||||
body = {}
|
||||
if search:
|
||||
body["search"] = search
|
||||
if sort:
|
||||
body["sort"] = sort
|
||||
print(json.dumps(request("POST", "idr/v1/users/_search", params=params, 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,85 @@
|
||||
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
_UNITS = {"second": 1, "minute": 60, "hour": 3600, "day": 86400, "week": 604800, "month": 2592000, "year": 31536000}
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _rel_ms_range(text):
|
||||
if not text:
|
||||
return None, None
|
||||
num = unit = None
|
||||
for t in str(text).lower().replace("last", "").split():
|
||||
if t.isdigit():
|
||||
num = int(t)
|
||||
elif t.rstrip("s") in _UNITS:
|
||||
unit = t.rstrip("s")
|
||||
if num is None or not unit:
|
||||
return None, None
|
||||
now_ms = int(time.time() * 1000)
|
||||
return now_ms - num * _UNITS[unit] * 1000, now_ms
|
||||
|
||||
|
||||
def request(method, url, params=None):
|
||||
base, headers = _cfg()
|
||||
full = url if url.startswith("http") else base + url.lstrip("/")
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
full += ("&" if "?" in full else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
req = urllib.request.Request(full, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _collect(first):
|
||||
# Follow the query's pagination links until no more pages remain.
|
||||
events = []
|
||||
queue = [first]
|
||||
while queue:
|
||||
page = queue.pop(0)
|
||||
events.extend(page.get("events", []) or [])
|
||||
for link in page.get("links", []) or []:
|
||||
href = link.get("href")
|
||||
if href:
|
||||
queue.append(request("GET", href))
|
||||
return events
|
||||
|
||||
|
||||
def run():
|
||||
inp = _inputs()
|
||||
start_time = inp.get("start_time")
|
||||
end_time = inp.get("end_time")
|
||||
if inp.get("time_range"):
|
||||
start_time, end_time = _rel_ms_range(inp.get("time_range"))
|
||||
params = {
|
||||
"query": inp.get("query"),
|
||||
"from": start_time,
|
||||
"to": end_time,
|
||||
"per_page": inp.get("logs_per_page"),
|
||||
"sequence_number": inp.get("sequence_number"),
|
||||
}
|
||||
log_id = inp.get("log_id", "")
|
||||
first = request("GET", "log_search/query/logs/" + urllib.parse.quote(log_id, safe=""), params=params)
|
||||
print(json.dumps({"events": _collect(first)}))
|
||||
|
||||
|
||||
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,84 @@
|
||||
import json, os, sys, time, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
_UNITS = {"second": 1, "minute": 60, "hour": 3600, "day": 86400, "week": 604800, "month": 2592000, "year": 31536000}
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _rel_ms_range(text):
|
||||
if not text:
|
||||
return None, None
|
||||
num = unit = None
|
||||
for t in str(text).lower().replace("last", "").split():
|
||||
if t.isdigit():
|
||||
num = int(t)
|
||||
elif t.rstrip("s") in _UNITS:
|
||||
unit = t.rstrip("s")
|
||||
if num is None or not unit:
|
||||
return None, None
|
||||
now_ms = int(time.time() * 1000)
|
||||
return now_ms - num * _UNITS[unit] * 1000, now_ms
|
||||
|
||||
|
||||
def request(method, url, params=None):
|
||||
base, headers = _cfg()
|
||||
full = url if url.startswith("http") else base + url.lstrip("/")
|
||||
if params:
|
||||
clean = {k: v for k, v in params.items() if v not in (None, "")}
|
||||
if clean:
|
||||
full += ("&" if "?" in full else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
req = urllib.request.Request(full, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def _collect(first):
|
||||
events = []
|
||||
queue = [first]
|
||||
while queue:
|
||||
page = queue.pop(0)
|
||||
events.extend(page.get("events", []) or [])
|
||||
for link in page.get("links", []) or []:
|
||||
href = link.get("href")
|
||||
if href:
|
||||
queue.append(request("GET", href))
|
||||
return events
|
||||
|
||||
|
||||
def run():
|
||||
inp = _inputs()
|
||||
start_time = inp.get("start_time")
|
||||
end_time = inp.get("end_time")
|
||||
if inp.get("time_range"):
|
||||
start_time, end_time = _rel_ms_range(inp.get("time_range"))
|
||||
params = {
|
||||
"query": inp.get("query"),
|
||||
"from": start_time,
|
||||
"to": end_time,
|
||||
"per_page": inp.get("logs_per_page"),
|
||||
"sequence_number": inp.get("sequence_number"),
|
||||
}
|
||||
log_set_id = inp.get("log_set_id", "")
|
||||
first = request("GET", "log_search/query/logsets/" + urllib.parse.quote(log_set_id, safe=""), params=params)
|
||||
print(json.dumps({"events": _collect(first)}))
|
||||
|
||||
|
||||
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,73 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
return base, headers
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _list(v):
|
||||
if v in (None, ""):
|
||||
return []
|
||||
if isinstance(v, list):
|
||||
return [str(x).strip() for x in v if str(x).strip()]
|
||||
return [p.strip() for p in str(v).split(",") if p.strip()]
|
||||
|
||||
|
||||
def _prune(d):
|
||||
return {k: v for k, v in d.items() if v}
|
||||
|
||||
|
||||
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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
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=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
# replace deletes the threat's existing indicators before adding the supplied ones.
|
||||
ACTION = "replace"
|
||||
|
||||
|
||||
def run():
|
||||
inp = _inputs()
|
||||
body = _prune({
|
||||
"ips": _list(inp.get("ip_addresses")),
|
||||
"hashes": _list(inp.get("hashes")),
|
||||
"domain_names": _list(inp.get("domain_names")),
|
||||
"urls": _list(inp.get("url")),
|
||||
})
|
||||
results = []
|
||||
for key in _list(inp.get("key")):
|
||||
out = request(
|
||||
"POST",
|
||||
"idr/v1/customthreats/key/" + urllib.parse.quote(key, safe="") + "/indicators/" + ACTION,
|
||||
params={"format": "json"},
|
||||
body=body,
|
||||
)
|
||||
results.append(out.get("threat", out))
|
||||
print(json.dumps({"data": results}))
|
||||
|
||||
|
||||
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,92 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
CONTAINS = "CONTAINS"
|
||||
EQUALS = "EQUALS"
|
||||
# (field, operator) pairs mirrored from the InsightIDR investigation search schema.
|
||||
SEARCH = [
|
||||
("actor_asset_hostname", CONTAINS),
|
||||
("actor_user_name", CONTAINS),
|
||||
("alert_mitre_t_codes", EQUALS),
|
||||
("alert_rule_rrn", EQUALS),
|
||||
("assignee_id", EQUALS),
|
||||
("organization_id", EQUALS),
|
||||
("priority", EQUALS),
|
||||
("rrn", EQUALS),
|
||||
("source", EQUALS),
|
||||
("status", EQUALS),
|
||||
("title", CONTAINS),
|
||||
]
|
||||
SORT_FIELDS = {
|
||||
"Created time": "created_time",
|
||||
"Priority": "priority",
|
||||
"RRN": "rrn",
|
||||
"Alert created time": "alerts_most_recent_created_time",
|
||||
"Alert detection created time": "alerts_most_recent_detection_created_time",
|
||||
}
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
multi = str(s.get("is_multi_customer") or "").strip().lower() in ("1", "true", "yes")
|
||||
return base, headers, multi
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _list(v):
|
||||
if v in (None, ""):
|
||||
return []
|
||||
if isinstance(v, list):
|
||||
return [str(x).strip() for x in v if str(x).strip()]
|
||||
return [p.strip() for p in str(v).split(",") if p.strip()]
|
||||
|
||||
|
||||
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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
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=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
_, _, multi = _cfg()
|
||||
inp = _inputs()
|
||||
search = []
|
||||
for field, op in SEARCH:
|
||||
for value in _list(inp.get(field)):
|
||||
search.append({"field": field, "operator": op, "value": value})
|
||||
direction = str(inp.get("sort_direction") or "asc").upper()
|
||||
sort = [{"field": SORT_FIELDS.get(f, f), "order": direction} for f in _list(inp.get("sort"))]
|
||||
body = {"search": search, "sort": sort}
|
||||
if inp.get("start_time"):
|
||||
body["start_time"] = inp["start_time"]
|
||||
if inp.get("end_time"):
|
||||
body["end_time"] = inp["end_time"]
|
||||
params = {
|
||||
"index": inp.get("index") or "0",
|
||||
"size": inp.get("page_size") or inp.get("limit") or 50,
|
||||
"multi-customer": "true" if multi else None,
|
||||
}
|
||||
print(json.dumps(request("POST", "idr/v2/investigations/_search", params=params, 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,85 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
multi = str(s.get("is_multi_customer") or "").strip().lower() in ("1", "true", "yes")
|
||||
is_v2 = str(s.get("is_v2") or "true").strip().lower() in ("1", "true", "yes")
|
||||
return base, headers, multi, is_v2
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _api_version(inp, is_v2):
|
||||
v = str(inp.get("api_version") or "Default").strip()
|
||||
return v if v in ("V1", "V2") else ("V2" if is_v2 else "V1")
|
||||
|
||||
|
||||
def _list(v):
|
||||
if v in (None, ""):
|
||||
return []
|
||||
if isinstance(v, list):
|
||||
return [str(x).strip() for x in v if str(x).strip()]
|
||||
return [p.strip() for p in str(v).split(",") if p.strip()]
|
||||
|
||||
|
||||
def _prune(d):
|
||||
return {k: v for k, v in d.items() if v not in (None, "", {}, [])}
|
||||
|
||||
|
||||
def _camel(text):
|
||||
return "".join(w.capitalize() for w in str(text).split()) if text else None
|
||||
|
||||
|
||||
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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
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=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
_, _, multi, is_v2 = _cfg()
|
||||
inp = _inputs()
|
||||
api_version = _api_version(inp, is_v2)
|
||||
status = inp.get("status", "")
|
||||
body = {}
|
||||
params = {}
|
||||
if api_version == "V2":
|
||||
body = _prune({
|
||||
"disposition": inp.get("disposition"),
|
||||
"threat_command_close_reason": _camel(inp.get("threat_command_close_reason")),
|
||||
"threat_command_free_text": inp.get("threat_command_free_text"),
|
||||
})
|
||||
params = {"multi-customer": "true" if multi else None}
|
||||
results = []
|
||||
for inv_id in _list(inp.get("investigation_id")):
|
||||
results.append(request(
|
||||
"PUT",
|
||||
"idr/" + api_version.lower() + "/investigations/" + urllib.parse.quote(inv_id, safe="") + "/status/" + urllib.parse.quote(status, safe=""),
|
||||
params=params,
|
||||
body=body or None,
|
||||
))
|
||||
print(json.dumps({"data": results}))
|
||||
|
||||
|
||||
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,46 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
multi = str(s.get("is_multi_customer") or "").strip().lower() in ("1", "true", "yes")
|
||||
is_v2 = str(s.get("is_v2") or "true").strip().lower() in ("1", "true", "yes")
|
||||
return base, headers, multi, is_v2
|
||||
|
||||
|
||||
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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
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 r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
request("GET", "idr/v1/investigations", params={"size": 1})
|
||||
print(json.dumps({"ok": True}))
|
||||
|
||||
|
||||
try:
|
||||
run()
|
||||
except urllib.error.HTTPError as e:
|
||||
detail = e.read().decode("utf-8", "replace")
|
||||
if e.code == 401:
|
||||
print(json.dumps({"ok": False, "error": "API key is not valid."}))
|
||||
elif e.code == 500:
|
||||
print(json.dumps({"ok": False, "error": "Wrong account region."}))
|
||||
else:
|
||||
print(json.dumps({"ok": False, "error": "HTTP " + str(e.code), "detail": detail}))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(json.dumps({"ok": False, "error": str(e)}))
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,65 @@
|
||||
import json, os, sys, urllib.request, urllib.parse, urllib.error
|
||||
|
||||
|
||||
def _cfg():
|
||||
s = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}"))
|
||||
region = str(s.get("region") or "us").strip().lower()
|
||||
base = "https://" + region + ".api.insight.rapid7.com/"
|
||||
headers = {"X-Api-Key": s.get("api_key", ""), "Content-Type": "application/json", "Accept": "application/json"}
|
||||
multi = str(s.get("is_multi_customer") or "").strip().lower() in ("1", "true", "yes")
|
||||
return base, headers, multi
|
||||
|
||||
|
||||
def _inputs():
|
||||
return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}"))
|
||||
|
||||
|
||||
def _prune(d):
|
||||
return {k: v for k, v in d.items() if v not in (None, "", {}, [])}
|
||||
|
||||
|
||||
def _camel(text):
|
||||
return "".join(w.capitalize() for w in str(text).split()) if text else None
|
||||
|
||||
|
||||
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 += ("&" if "?" in url else "?") + urllib.parse.urlencode(clean, doseq=True)
|
||||
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=90) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
|
||||
def run():
|
||||
_, _, multi = _cfg()
|
||||
inp = _inputs()
|
||||
inv_id = inp.get("investigation_id", "")
|
||||
disposition = inp.get("disposition")
|
||||
body = _prune({
|
||||
"assignee": _prune({"email": inp.get("user_email_address")}),
|
||||
"disposition": disposition.replace(" ", "_") if disposition else None,
|
||||
"priority": inp.get("priority"),
|
||||
"status": inp.get("status"),
|
||||
"title": inp.get("title"),
|
||||
"threat_command_close_reason": _camel(inp.get("threat_command_close_reason")),
|
||||
"threat_command_free_text": inp.get("threat_command_free_text"),
|
||||
})
|
||||
out = request("PATCH", "idr/v2/investigations/" + urllib.parse.quote(inv_id, safe=""),
|
||||
params={"multi-customer": "true" if multi else None}, body=body)
|
||||
print(json.dumps(out))
|
||||
|
||||
|
||||
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