diff --git a/integrations/crowdstrike/manifest.yaml b/integrations/crowdstrike/manifest.yaml index ec78232..5a612c1 100644 --- a/integrations/crowdstrike/manifest.yaml +++ b/integrations/crowdstrike/manifest.yaml @@ -226,3 +226,810 @@ commands: timeout: { type: number, description: "Command timeout in seconds (default 30)." } required: [host_id, process_ids] outputs_schema: { properties: {} } + + - id: search_custom_iocs + name: crowdstrike-search-custom-iocs + description: "Search custom IOC indicators using optional FQL filter parts (type, value, source, expiration)." + risk: read + inputs_schema: + properties: + types: { type: string, description: "Indicator type filter value, e.g. domain or sha256." } + values: { type: string, description: "Indicator value filter." } + sources: { type: string, description: "Source label filter." } + expiration: { type: string, description: "Expiration date filter (ISO 8601)." } + limit: { type: string, description: "Max results to return (default 50)." } + sort: { type: string, description: "Sort expression, e.g. created_on.desc." } + offset: { type: string, description: "Pagination offset." } + required: [] + outputs_schema: { properties: {} } + + - id: get_custom_ioc + name: crowdstrike-get-custom-ioc + description: "Retrieve a custom IOC indicator by its ID." + risk: read + inputs_schema: + properties: + ioc_id: { type: string, description: "IOC indicator ID to retrieve." } + required: [ioc_id] + outputs_schema: { properties: {} } + + - id: update_custom_ioc + name: crowdstrike-update-custom-ioc + description: "Update an existing custom IOC indicator (action, platforms, severity, source, description, expiration)." + risk: destructive + inputs_schema: + properties: + ioc_id: { type: string, description: "IOC indicator ID to update." } + action: { type: string, description: "Action: no_action, allow, prevent_no_ui, prevent, or detect." } + platforms: { type: string, description: "Comma-separated platforms: windows, mac, linux." } + severity: { type: string, description: "Severity: informational, low, medium, high, or critical." } + source: { type: string, description: "Source label." } + description: { type: string, description: "Description text." } + expiration: { type: string, description: "Expiration date (ISO 8601)." } + required: [ioc_id] + outputs_schema: { properties: {} } + + - id: batch_upload_custom_ioc + name: crowdstrike-batch-upload-custom-ioc + description: "Create multiple custom IOC indicators in a single call by providing a JSON array of indicator objects." + risk: destructive + inputs_schema: + properties: + multiple_indicators_json: { type: string, description: "JSON array string of indicator objects to create." } + required: [multiple_indicators_json] + outputs_schema: { properties: {} } + + - id: processes_ran_on + name: crowdstrike-processes-ran-on + description: "List process IDs that ran a given indicator on a specific device." + risk: read + inputs_schema: + properties: + type: { type: string, description: "Indicator type: sha256, sha1, md5, domain, ipv4, or ipv6." } + value: { type: string, description: "Indicator value." } + device_id: { type: string, description: "Device ID to scope the query." } + required: [type, value, device_id] + outputs_schema: { properties: {} } + + - id: process_details + name: crowdstrike-process-details + description: "Retrieve full details for one or more processes by process ID." + risk: read + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated process IDs." } + required: [ids] + outputs_schema: { properties: {} } + + - id: device_ran_on + name: crowdstrike-device-ran-on + description: "List device IDs on which a given indicator has been observed." + risk: read + inputs_schema: + properties: + type: { type: string, description: "Indicator type: sha256, sha1, md5, domain, ipv4, or ipv6." } + value: { type: string, description: "Indicator value." } + required: [type, value] + outputs_schema: { properties: {} } + + - id: create_host_group + name: crowdstrike-create-host-group + description: "Create a new host group (static or dynamic) with an optional description and assignment rule." + risk: safe_write + inputs_schema: + properties: + name: { type: string, description: "Name for the new host group." } + group_type: { type: string, description: "Group type: static or dynamic." } + description: { type: string, description: "Optional description for the host group." } + assignment_rule: { type: string, description: "Optional FQL assignment rule (required for dynamic groups)." } + required: [name, group_type] + outputs_schema: { properties: {} } + + - id: update_host_group + name: crowdstrike-update-host-group + description: "Update an existing host group's name, description, or assignment rule." + risk: safe_write + inputs_schema: + properties: + host_group_id: { type: string, description: "ID of the host group to update." } + name: { type: string, description: "New name for the host group." } + description: { type: string, description: "New description for the host group." } + assignment_rule: { type: string, description: "New FQL assignment rule for dynamic groups." } + required: [host_group_id] + outputs_schema: { properties: {} } + + - id: delete_host_groups + name: crowdstrike-delete-host-groups + description: "Delete one or more host groups by ID." + risk: destructive + inputs_schema: + properties: + host_group_id: { type: string, description: "Comma-separated host group IDs to delete." } + required: [host_group_id] + outputs_schema: { properties: {} } + + - id: list_host_group_members + name: crowdstrike-list-host-group-members + description: "List devices that are members of a host group, with optional FQL filter and pagination." + risk: read + inputs_schema: + properties: + host_group_id: { type: string, description: "Host group ID to list members of." } + filter: { type: string, description: "Optional FQL filter to narrow results." } + limit: { type: string, description: "Max members to return (default 50)." } + offset: { type: string, description: "Pagination offset." } + sort: { type: string, description: "Sort expression, e.g. hostname.asc." } + required: [] + outputs_schema: { properties: {} } + + - id: add_host_group_members + name: crowdstrike-add-host-group-members + description: "Add one or more hosts to a host group by device ID." + risk: safe_write + inputs_schema: + properties: + host_group_id: { type: string, description: "ID of the host group to add hosts to." } + host_ids: { type: string, description: "Comma-separated device IDs to add." } + required: [host_group_id, host_ids] + outputs_schema: { properties: {} } + + - id: remove_host_group_members + name: crowdstrike-remove-host-group-members + description: "Remove one or more hosts from a host group by device ID." + risk: safe_write + inputs_schema: + properties: + host_group_id: { type: string, description: "ID of the host group to remove hosts from." } + host_ids: { type: string, description: "Comma-separated device IDs to remove." } + required: [host_group_id, host_ids] + outputs_schema: { properties: {} } + + # ── Cases ───────────────────────────────────────────────────────────────── + - id: list_case_summaries + name: crowdstrike-list-case-summaries + description: "List case summaries. If ids are provided, fetches those specific cases; otherwise queries all cases sorted by creation date and returns their summaries." + risk: read + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated case IDs (optional). If omitted, all cases are listed." } + required: [] + outputs_schema: { properties: {} } + + - id: get_evidence_for_case + name: crowdstrike-get-evidence-for-case + description: "Retrieve full case details including evidence for a single case by ID. Evidence is available at resources[0].evidence in the response." + risk: read + inputs_schema: + properties: + id: { type: string, description: "Case ID." } + required: [id] + outputs_schema: { properties: {} } + + - id: add_case_tag + name: crowdstrike-add-case-tag + description: "Add one or more tags to an existing case." + risk: safe_write + inputs_schema: + properties: + id: { type: string, description: "Case ID." } + tags: { type: string, description: "Comma-separated tags to add." } + required: [id, tags] + outputs_schema: { properties: {} } + + - id: delete_case_tag + name: crowdstrike-delete-case-tag + description: "Remove a single tag from an existing case." + risk: safe_write + inputs_schema: + properties: + id: { type: string, description: "Case ID." } + tag: { type: string, description: "Tag to remove." } + required: [id, tag] + outputs_schema: { properties: {} } + + - id: resolve_case + name: crowdstrike-resolve-case + description: "Update a case: change status, name, description, assigned analyst, or severity." + risk: destructive + inputs_schema: + properties: + id: { type: string, description: "Case ID." } + status: { type: string, description: "New case status (optional)." } + name: { type: string, description: "New case name (optional)." } + assigned_to_uuid: { type: string, description: "UUID of the analyst to assign the case to (optional)." } + description: { type: string, description: "Updated case description (optional)." } + severity: { type: number, description: "Severity level as an integer (optional)." } + required: [id] + outputs_schema: { properties: {} } + + - id: list_detection_summaries + name: crowdstrike-list-detection-summaries + description: "List alert/detection summaries. Accepts explicit composite IDs or an FQL filter to query alerts first, then fetches full entities." + risk: read + inputs_schema: + properties: + fetch_query: { type: string, description: "FQL filter to query alerts (optional, used when ids are not provided)." } + ids: { type: string, description: "Comma-separated alert composite IDs (optional). Takes priority over fetch_query." } + required: [] + outputs_schema: { properties: {} } + + - id: endpoint + name: crowdstrike-endpoint + description: "Look up a device/endpoint by device ID, local IP, or hostname; returns full device details for matches." + risk: read + inputs_schema: + properties: + id: { type: string, description: "Device ID to match (optional)." } + ip: { type: string, description: "Local IP address to match (optional)." } + hostname: { type: string, description: "Hostname to match (optional)." } + required: [] + outputs_schema: { properties: {} } + +# RTR script/file management and extended Real Time Response commands +# Fragment — matches manifest.yaml command block format + + - id: upload_script + name: crowdstrike-upload-script + description: "Upload a PowerShell script to the RTR cloud script library." + risk: safe_write + inputs_schema: + properties: + name: { type: string, description: "Script name (unique identifier in the library)." } + permission_type: { type: string, description: "Visibility: private, group, or public (default private)." } + content: { type: string, description: "PowerShell script content." } + required: [name, content] + outputs_schema: { properties: {} } + + - id: get_script + name: crowdstrike-get-script + description: "Retrieve one or more RTR cloud scripts by ID." + risk: read + inputs_schema: + properties: + script_id: { type: string, description: "Comma-separated script IDs." } + required: [script_id] + outputs_schema: { properties: {} } + + - id: delete_script + name: crowdstrike-delete-script + description: "Delete an RTR cloud script by ID." + risk: destructive + inputs_schema: + properties: + script_id: { type: string, description: "Script ID to delete." } + required: [script_id] + outputs_schema: { properties: {} } + + - id: list_scripts + name: crowdstrike-list-scripts + description: "List all RTR cloud scripts available in the tenant." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + + - id: list_files + name: crowdstrike-list-files + description: "List all RTR PUT files (files available for deployment to hosts)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + + - id: get_file + name: crowdstrike-get-file + description: "Retrieve one or more RTR PUT files by ID." + risk: read + inputs_schema: + properties: + file_id: { type: string, description: "Comma-separated PUT file IDs." } + required: [file_id] + outputs_schema: { properties: {} } + + - id: delete_file + name: crowdstrike-delete-file + description: "Delete an RTR PUT file by ID." + risk: destructive + inputs_schema: + properties: + file_id: { type: string, description: "PUT file ID to delete." } + required: [file_id] + outputs_schema: { properties: {} } + + - id: refresh_session + name: crowdstrike-refresh-session + description: "Refresh an existing RTR session for a host, extending its TTL." + risk: safe_write + inputs_schema: + properties: + host_id: { type: string, description: "Device ID of the host whose session to refresh." } + required: [host_id] + outputs_schema: { properties: {} } + + - id: list_host_files + name: crowdstrike-list-host-files + description: "List files extracted from an RTR session on a specific host. Opens a new session if no session_id is provided." + risk: read + inputs_schema: + properties: + host_id: { type: string, description: "Device ID of the target host." } + session_id: { type: string, description: "Existing RTR session ID (optional; a new session is opened if omitted)." } + required: [host_id] + outputs_schema: { properties: {} } + + - id: rtr_remove_file + name: crowdstrike-rtr-remove-file + description: "Open an RTR batch session and remove a file from one or more hosts using the rm command." + risk: destructive + inputs_schema: + properties: + host_ids: { type: string, description: "Comma-separated device IDs." } + file_path: { type: string, description: "Full path of the file to remove on the host." } + os: { type: string, description: "Target OS: Windows, Linux, or Mac." } + queue_offline: { type: string, description: "Queue command for offline hosts: true or false (default false)." } + timeout: { type: string, description: "Command timeout in seconds (default 30)." } + required: [host_ids, file_path, os] + outputs_schema: { properties: {} } + + - id: rtr_list_network_stats + name: crowdstrike-rtr-list-network-stats + description: "Open an RTR batch session on a single host and run netstat to list active network connections." + risk: read + inputs_schema: + properties: + host_id: { type: string, description: "Device ID of the target host." } + queue_offline: { type: string, description: "Queue command for offline hosts: true or false (default false)." } + timeout: { type: string, description: "Command timeout in seconds (default 30)." } + required: [host_id] + outputs_schema: { properties: {} } + + - id: rtr_list_scheduled_tasks + name: crowdstrike-rtr-list-scheduled-tasks + description: "Open an RTR batch session and enumerate scheduled tasks on Windows hosts via schtasks." + risk: read + inputs_schema: + properties: + host_ids: { type: string, description: "Comma-separated device IDs." } + queue_offline: { type: string, description: "Queue command for offline hosts: true or false (default false)." } + timeout: { type: string, description: "Command timeout in seconds (default 30)." } + required: [host_ids] + outputs_schema: { properties: {} } + + # ── ML Exclusions ────────────────────────────────────────────────────────── + - id: create_ml_exclusion + name: crowdstrike-create-ml-exclusion + description: "Create a new ML exclusion to prevent the machine learning engine from triggering on a specific value." + risk: destructive + inputs_schema: + properties: + value: { type: string, description: "The exclusion value (e.g. file path or hash)." } + excluded_from: { type: string, description: "Comma-separated list of ML detection categories to exclude from (e.g. blocking,extraction)." } + comment: { type: string, description: "Optional comment describing the reason for the exclusion." } + groups: { type: string, description: "Comma-separated host group IDs to scope the exclusion. Defaults to all groups." } + required: [value, excluded_from] + outputs_schema: { properties: {} } + + - id: update_ml_exclusion + name: crowdstrike-update-ml-exclusion + description: "Update an existing ML exclusion by ID (value, comment, or groups)." + risk: destructive + inputs_schema: + properties: + id: { type: string, description: "ML exclusion ID to update." } + value: { type: string, description: "New exclusion value (optional)." } + comment: { type: string, description: "Updated comment (optional)." } + groups: { type: string, description: "Comma-separated host group IDs to replace the current scoping (optional)." } + required: [id] + outputs_schema: { properties: {} } + + - id: delete_ml_exclusion + name: crowdstrike-delete-ml-exclusion + description: "Delete one or more ML exclusions by ID." + risk: destructive + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated ML exclusion IDs to delete." } + required: [ids] + outputs_schema: { properties: {} } + + - id: search_ml_exclusion + name: crowdstrike-search-ml-exclusion + description: "Search ML exclusions by explicit IDs, a value substring match, or an FQL filter; returns full exclusion entities." + risk: read + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated exclusion IDs (takes priority over value and filter)." } + value: { type: string, description: "Value substring to search for (takes priority over filter when ids is absent)." } + filter: { type: string, description: "FQL filter string (used when neither ids nor value is provided)." } + limit: { type: string, description: "Max results to return." } + offset: { type: string, description: "Pagination offset." } + sort: { type: string, description: "Sort expression, e.g. value.asc." } + required: [] + outputs_schema: { properties: {} } + + # ── IOA Exclusions ───────────────────────────────────────────────────────── + - id: create_ioa_exclusion + name: crowdstrike-create-ioa-exclusion + description: "Create a new IOA exclusion to suppress a specific Indicator of Attack pattern." + risk: destructive + inputs_schema: + properties: + exclusion_name: { type: string, description: "Display name for the IOA exclusion." } + pattern_id: { type: string, description: "ID of the IOA pattern to exclude." } + cl_regex: { type: string, description: "Command-line regular expression to match." } + ifn_regex: { type: string, description: "Image filename regular expression to match." } + pattern_name: { type: string, description: "Human-readable pattern name (optional)." } + comment: { type: string, description: "Optional comment." } + description: { type: string, description: "Optional description." } + groups: { type: string, description: "Comma-separated host group IDs to scope the exclusion. Defaults to all groups." } + detection_json: { type: string, description: "Optional detection JSON override." } + required: [exclusion_name, pattern_id, cl_regex, ifn_regex] + outputs_schema: { properties: {} } + + - id: update_ioa_exclusion + name: crowdstrike-update-ioa-exclusion + description: "Update an existing IOA exclusion by ID." + risk: destructive + inputs_schema: + properties: + id: { type: string, description: "IOA exclusion ID to update." } + exclusion_name: { type: string, description: "New display name (optional)." } + pattern_id: { type: string, description: "New pattern ID (optional)." } + pattern_name: { type: string, description: "New pattern name (optional)." } + cl_regex: { type: string, description: "New command-line regex (optional)." } + ifn_regex: { type: string, description: "New image filename regex (optional)." } + comment: { type: string, description: "Updated comment (optional)." } + description: { type: string, description: "Updated description (optional)." } + groups: { type: string, description: "Comma-separated host group IDs to replace current scoping (optional)." } + detection_json: { type: string, description: "Updated detection JSON (optional)." } + required: [id] + outputs_schema: { properties: {} } + + - id: delete_ioa_exclusion + name: crowdstrike-delete-ioa-exclusion + description: "Delete one or more IOA exclusions by ID." + risk: destructive + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated IOA exclusion IDs to delete." } + required: [ids] + outputs_schema: { properties: {} } + + - id: search_ioa_exclusion + name: crowdstrike-search-ioa-exclusion + description: "Search IOA exclusions by explicit IDs, a name substring match, or an FQL filter; returns full exclusion entities." + risk: read + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated exclusion IDs (takes priority over name and filter)." } + name: { type: string, description: "Name substring to search for (takes priority over filter when ids is absent)." } + filter: { type: string, description: "FQL filter string (used when neither ids nor name is provided)." } + limit: { type: string, description: "Max results to return." } + offset: { type: string, description: "Pagination offset." } + required: [] + outputs_schema: { properties: {} } + + # ── Quarantine ───────────────────────────────────────────────────────────── + - id: list_quarantined_file + name: crowdstrike-list-quarantined-file + description: "List quarantined files by explicit IDs or an FQL filter query." + risk: read + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated quarantined file IDs (takes priority over filter)." } + filter: { type: string, description: "FQL filter string (used when ids is absent)." } + limit: { type: string, description: "Max results to return (default 50)." } + offset: { type: string, description: "Pagination offset." } + required: [] + outputs_schema: { properties: {} } + + - id: apply_quarantine_file_action + name: crowdstrike-apply-quarantine-file-action + description: "Apply an action (delete, release, or unrelease) to one or more quarantined files." + risk: destructive + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated quarantined file IDs." } + action: { type: string, description: "Action to apply: delete, release, or unrelease." } + comment: { type: string, description: "Comment explaining the reason for the action." } + required: [ids, action, comment] + outputs_schema: { properties: {} } + + # ── Spotlight CVE ────────────────────────────────────────────────────────── + - id: cve + name: crowdstrike-cve + description: "Retrieve Falcon Spotlight vulnerability details for one or more CVE IDs, including CVE facet metadata." + risk: read + inputs_schema: + properties: + cve: { type: string, description: "Comma-separated CVE IDs to look up (e.g. CVE-2021-44228,CVE-2022-3786)." } + required: [cve] + outputs_schema: { properties: {} } + + - id: spotlight_list_host_by_vulnerability + name: crowdstrike-spotlight-list-host-by-vulnerability + description: "List hosts affected by one or more CVE IDs using Falcon Spotlight, with host_info facet details." + risk: read + inputs_schema: + properties: + cve_ids: { type: string, description: "Comma-separated CVE IDs to filter by (e.g. CVE-2021-44228)." } + limit: { type: string, description: "Max results to return (default 50)." } + required: [cve_ids] + outputs_schema: { properties: {} } + + # ── ODS — On-Demand Scans ──────────────────────────────────────────────────── + - id: ods_query_scan + name: crowdstrike-ods-query-scan + description: "Query ODS scans by ID or FQL filter. If ids provided, fetch those scans directly; otherwise query and resolve." + risk: read + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated scan IDs to retrieve directly." } + filter: { type: string, description: "FQL filter to narrow query results." } + limit: { type: string, description: "Max results to return." } + offset: { type: string, description: "Pagination offset." } + required: [] + outputs_schema: { properties: {} } + + - id: ods_query_scheduled_scan + name: crowdstrike-ods-query-scheduled-scan + description: "Query ODS scheduled scans by ID or FQL filter. If ids provided, fetch those scans directly; otherwise query and resolve." + risk: read + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated scheduled scan IDs to retrieve directly." } + filter: { type: string, description: "FQL filter to narrow query results." } + limit: { type: string, description: "Max results to return." } + offset: { type: string, description: "Pagination offset." } + required: [] + outputs_schema: { properties: {} } + + - id: ods_query_scan_host + name: crowdstrike-ods-query-scan-host + description: "Query ODS scan hosts by ID or FQL filter. If ids provided, fetch those scan-host records directly; otherwise query and resolve." + risk: read + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated scan-host IDs to retrieve directly." } + filter: { type: string, description: "FQL filter to narrow query results." } + limit: { type: string, description: "Max results to return." } + offset: { type: string, description: "Pagination offset." } + required: [] + outputs_schema: { properties: {} } + + - id: ods_query_malicious_files + name: crowdstrike-ods-query-malicious-files + description: "Query ODS malicious files by file_ids or FQL filter. If file_ids provided, fetch those records directly; otherwise query and resolve." + risk: read + inputs_schema: + properties: + file_ids: { type: string, description: "Comma-separated malicious file IDs to retrieve directly." } + filter: { type: string, description: "FQL filter to narrow query results." } + limit: { type: string, description: "Max results to return." } + offset: { type: string, description: "Pagination offset." } + required: [] + outputs_schema: { properties: {} } + + - id: ods_create_scan + name: crowdstrike-ods-create-scan + description: "Create an ODS on-demand scan targeting specific hosts and/or host groups." + risk: destructive + inputs_schema: + properties: + hosts: { type: string, description: "Comma-separated device IDs to scan." } + host_groups: { type: string, description: "Comma-separated host group IDs to scan." } + file_paths: { type: string, description: "Comma-separated file paths to include in the scan." } + scan_inclusions: { type: string, description: "Comma-separated scan inclusion patterns." } + description: { type: string, description: "Optional description for the scan." } + quarantine: { type: string, description: "Quarantine detected malicious files: true or false." } + required: [] + outputs_schema: { properties: {} } + + - id: ods_create_scheduled_scan + name: crowdstrike-ods-create-scheduled-scan + description: "Create a recurring ODS scheduled scan for one or more host groups." + risk: destructive + inputs_schema: + properties: + host_groups: { type: string, description: "Comma-separated host group IDs (required)." } + file_paths: { type: string, description: "Comma-separated file paths to include." } + scan_inclusions: { type: string, description: "Comma-separated scan inclusion patterns." } + description: { type: string, description: "Optional description." } + schedule_start_timestamp: { type: string, description: "ISO 8601 start timestamp (required)." } + schedule_interval: { type: string, description: "Recurrence: Never, Daily, Weekly, Every other week, Every four weeks, or Monthly (required)." } + required: [host_groups, schedule_start_timestamp, schedule_interval] + outputs_schema: { properties: {} } + + - id: ods_delete_scheduled_scan + name: crowdstrike-ods-delete-scheduled-scan + description: "Delete one or more ODS scheduled scans by ID." + risk: destructive + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated scheduled scan IDs to delete." } + required: [ids] + outputs_schema: { properties: {} } + + # ── CSPM — Cloud Security Posture Management ────────────────────────────────── + - id: cspm_list_policy_details + name: crowdstrike-cspm-list-policy-details + description: "Retrieve CSPM policy details for one or more policy IDs." + risk: read + inputs_schema: + properties: + policy_ids: { type: string, description: "Comma-separated CSPM policy IDs." } + required: [policy_ids] + outputs_schema: { properties: {} } + + - id: cspm_list_service_policy_settings + name: crowdstrike-cspm-list-service-policy-settings + description: "List CSPM service policy settings for a given policy ID and cloud platform." + risk: read + inputs_schema: + properties: + policy_id: { type: string, description: "CSPM policy ID." } + cloud_platform: { type: string, description: "Cloud platform: aws (default), azure, or gcp." } + service: { type: string, description: "Optional cloud service name to filter." } + limit: { type: string, description: "Max results to return." } + required: [policy_id] + outputs_schema: { properties: {} } + + - id: cspm_update_policy_settings + name: crowdstrike-cspm-update-policy-settings + description: "Update CSPM policy settings: enable/disable, change severity, scope to regions or account." + risk: destructive + inputs_schema: + properties: + policy_id: { type: string, description: "CSPM policy ID to update (required)." } + account_id: { type: string, description: "Optional cloud account ID to scope the update." } + enabled: { type: string, description: "Enable policy: true (default) or false." } + regions: { type: string, description: "Comma-separated regions to scope the policy." } + severity: { type: string, description: "Optional severity override." } + tag_excluded: { type: string, description: "Exclude tagged resources: true or false." } + required: [policy_id] + outputs_schema: { properties: {} } + + # ── Users / Rules / Alerts ──────────────────────────────────────────────────── + - id: list_users + name: crowdstrike-list-users + description: "List Falcon users. If id provided, fetch those users directly; otherwise query with optional filter." + risk: read + inputs_schema: + properties: + id: { type: string, description: "Comma-separated user UUIDs to retrieve directly." } + filter: { type: string, description: "FQL filter to narrow query results." } + offset: { type: string, description: "Pagination offset (default 0)." } + limit: { type: string, description: "Max results to return (default 50)." } + required: [] + outputs_schema: { properties: {} } + + - id: get_ioarules + name: crowdstrike-get-ioarules + description: "Retrieve IOA (Indicator of Attack) custom rules by rule ID." + risk: read + inputs_schema: + properties: + rule_ids: { type: string, description: "Comma-separated IOA rule IDs." } + required: [rule_ids] + outputs_schema: { properties: {} } + + - id: list_cnapp_alerts + name: crowdstrike-list-cnapp-alerts + description: "List CNAPP container security alerts, optionally filtered by FQL expression." + risk: read + inputs_schema: + properties: + filter: { type: string, description: "Optional FQL filter to narrow results." } + required: [] + outputs_schema: { properties: {} } + + - id: resolve_identity_detection + name: crowdstrike-resolve-identity-detection + description: "Resolve or update Falcon Identity Detection alerts: change status, assign, comment, tag, or unassign." + risk: destructive + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated composite alert IDs (required)." } + update_status: { type: string, description: "New status, e.g. new, in_progress, closed, or reopened." } + assign_to_uuid: { type: string, description: "Analyst UUID to assign the detection to." } + append_comment: { type: string, description: "Comment text to append." } + add_tag: { type: string, description: "Tag to add to the detection." } + remove_tag: { type: string, description: "Tag to remove from the detection." } + unassign: { type: string, description: "Unassign the detection: true or false." } + show_in_ui: { type: string, description: "Show in UI: true or false." } + required: [ids] + outputs_schema: { properties: {} } + + - id: resolve_mobile_detection + name: crowdstrike-resolve-mobile-detection + description: "Resolve or update Falcon Mobile Detection alerts: change status, assign, comment, tag, or unassign." + risk: destructive + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated composite alert IDs (required)." } + update_status: { type: string, description: "New status, e.g. new, in_progress, closed, or reopened." } + assign_to_uuid: { type: string, description: "Analyst UUID to assign the detection to." } + append_comment: { type: string, description: "Comment text to append." } + add_tag: { type: string, description: "Tag to add to the detection." } + remove_tag: { type: string, description: "Tag to remove from the detection." } + unassign: { type: string, description: "Unassign the detection: true or false." } + show_in_ui: { type: string, description: "Show in UI: true or false." } + required: [ids] + outputs_schema: { properties: {} } + + - id: list_workflow_definitions + name: crowdstrike-list-workflow-definitions + description: "List Falcon Fusion workflow definitions using an optional FQL filter, pagination offset/limit, and sort expression." + risk: read + inputs_schema: + properties: + filter: { type: string, description: "FQL filter expression." } + limit: { type: string, description: "Maximum number of results (default 50)." } + offset: { type: string, description: "Pagination offset (default 0)." } + sort: { type: string, description: "Sort expression, e.g. name.asc." } + required: [] + outputs_schema: { properties: {} } + + - id: workflow_execute + name: crowdstrike-workflow-execute + description: "Trigger execution of one or more Falcon Fusion workflow definitions." + risk: destructive + inputs_schema: + properties: + definition_id: { type: string, description: "Comma-separated workflow definition IDs to execute." } + name: { type: string, description: "Optional workflow name filter." } + key: { type: string, description: "Optional idempotency key for the execution." } + source_event_url: { type: string, description: "Optional source event URL." } + execution_cid: { type: string, description: "Comma-separated CIDs to target for execution." } + body: { type: string, description: "JSON string body to pass as the execution payload (default {})." } + required: [definition_id] + outputs_schema: { properties: {} } + + - id: list_workflow_executions + name: crowdstrike-list-workflow-executions + description: "List Falcon Fusion workflow execution records using an optional FQL filter, pagination offset/limit, and sort expression." + risk: read + inputs_schema: + properties: + filter: { type: string, description: "FQL filter expression." } + limit: { type: string, description: "Maximum number of results (default 50)." } + offset: { type: string, description: "Pagination offset (default 0)." } + sort: { type: string, description: "Sort expression, e.g. start_timestamp.desc." } + required: [] + outputs_schema: { properties: {} } + + - id: list_workflow_execution_results + name: crowdstrike-list-workflow-execution-results + description: "Retrieve detailed results for one or more workflow execution IDs." + risk: read + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated workflow execution IDs." } + required: [ids] + outputs_schema: { properties: {} } + + - id: workflow_execution_action + name: crowdstrike-workflow-execution-action + description: "Perform an action (cancel or resume) on one or more workflow executions." + risk: destructive + inputs_schema: + properties: + ids: { type: string, description: "Comma-separated workflow execution IDs." } + action_name: { type: string, description: "Action to perform: cancel or resume.", enum: [cancel, resume] } + required: [ids, action_name] + outputs_schema: { properties: {} } + + - id: list_identity_entities + name: crowdstrike-list-identity-entities + description: "Query Falcon Identity Protection entities (users or endpoints) via the GraphQL API, returning risk scores and factors." + risk: read + inputs_schema: + properties: + type: { type: string, description: "Entity type: USER or ENDPOINT.", enum: [USER, ENDPOINT] } + limit: { type: string, description: "Maximum number of nodes to return (default 50)." } + primary_display_name: { type: string, description: "Optional filter by primary display name (informational; include in query if supported)." } + email: { type: string, description: "Optional filter by email address (informational; include in query if supported)." } + required: [type] + outputs_schema: { properties: {} } diff --git a/integrations/crowdstrike/scripts/add_case_tag.py b/integrations/crowdstrike/scripts/add_case_tag.py new file mode 100644 index 0000000..5c48d8d --- /dev/null +++ b/integrations/crowdstrike/scripts/add_case_tag.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + id_ = INPUTS.get("id", "") + tags = INPUTS.get("tags", "") + result = call("POST", "/cases/entities/case-tags/v1", tok, body={"id": id_, "tags": csv(tags)}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/add_host_group_members.py b/integrations/crowdstrike/scripts/add_host_group_members.py new file mode 100644 index 0000000..d6a2b07 --- /dev/null +++ b/integrations/crowdstrike/scripts/add_host_group_members.py @@ -0,0 +1,58 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_group_id = INPUTS.get("host_group_id", "") + host_ids = INPUTS.get("host_ids", "") + result = call( + "POST", + "/devices/entities/host-group-actions/v1", + tok, + params=[("action_name", "add-hosts")], + body={ + "action_parameters": [{"name": "filter", "value": "(device_id:" + str(csv(host_ids)) + ")"}], + "ids": [host_group_id], + }, + ) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/apply_quarantine_file_action.py b/integrations/crowdstrike/scripts/apply_quarantine_file_action.py new file mode 100644 index 0000000..1e5a425 --- /dev/null +++ b/integrations/crowdstrike/scripts/apply_quarantine_file_action.py @@ -0,0 +1,55 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + action = INPUTS.get("action", "") + comment = INPUTS.get("comment", "") + body = { + "ids": csv(ids), + "action": action, + "comment": comment, + } + result = call("PATCH", "/quarantine/entities/quarantined-files/v1", tok, body=body) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/batch_upload_custom_ioc.py b/integrations/crowdstrike/scripts/batch_upload_custom_ioc.py new file mode 100644 index 0000000..5efe143 --- /dev/null +++ b/integrations/crowdstrike/scripts/batch_upload_custom_ioc.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + parsed = json.loads(INPUTS.get("multiple_indicators_json", "[]")) + result = call("POST", "/iocs/entities/indicators/v1", tok, body={"indicators": parsed}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/create_host_group.py b/integrations/crowdstrike/scripts/create_host_group.py new file mode 100644 index 0000000..4a74100 --- /dev/null +++ b/integrations/crowdstrike/scripts/create_host_group.py @@ -0,0 +1,56 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + name = INPUTS.get("name", "") + group_type = INPUTS.get("group_type", "") + description = INPUTS.get("description", "") + assignment_rule = INPUTS.get("assignment_rule", "") + res = {"name": name, "group_type": group_type} + if description: + res["description"] = description + if assignment_rule: + res["assignment_rule"] = assignment_rule + result = call("POST", "/devices/entities/host-groups/v1", tok, body={"resources": [res]}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/create_ioa_exclusion.py b/integrations/crowdstrike/scripts/create_ioa_exclusion.py new file mode 100644 index 0000000..88e4e20 --- /dev/null +++ b/integrations/crowdstrike/scripts/create_ioa_exclusion.py @@ -0,0 +1,73 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + exclusion_name = INPUTS.get("exclusion_name", "") + pattern_id = INPUTS.get("pattern_id", "") + cl_regex = INPUTS.get("cl_regex", "") + ifn_regex = INPUTS.get("ifn_regex", "") + pattern_name = INPUTS.get("pattern_name", "") + comment = INPUTS.get("comment", "") + description = INPUTS.get("description", "") + groups = INPUTS.get("groups", "") + detection_json = INPUTS.get("detection_json", "") + + body = { + "name": exclusion_name, + "pattern_id": pattern_id, + "cl_regex": cl_regex, + "ifn_regex": ifn_regex, + "groups": csv(groups or "all"), + } + if pattern_name: + body["pattern_name"] = pattern_name + if comment: + body["comment"] = comment + if description: + body["description"] = description + if detection_json: + body["detection_json"] = detection_json + + result = call("POST", "/policy/entities/ioa-exclusions/v1", tok, body=body) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/create_ml_exclusion.py b/integrations/crowdstrike/scripts/create_ml_exclusion.py new file mode 100644 index 0000000..ffc3dcf --- /dev/null +++ b/integrations/crowdstrike/scripts/create_ml_exclusion.py @@ -0,0 +1,58 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + value = INPUTS.get("value", "") + excluded_from = INPUTS.get("excluded_from", "") + comment = INPUTS.get("comment", "") + groups = INPUTS.get("groups", "") + body = { + "value": value, + "excluded_from": csv(excluded_from), + "groups": csv(groups or "all"), + } + if comment: + body["comment"] = comment + result = call("POST", "/policy/entities/ml-exclusions/v1", tok, body=body) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/cspm_list_policy_details.py b/integrations/crowdstrike/scripts/cspm_list_policy_details.py new file mode 100644 index 0000000..b573052 --- /dev/null +++ b/integrations/crowdstrike/scripts/cspm_list_policy_details.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + policy_ids = INPUTS.get("policy_ids", "") + result = call("GET", "/settings/entities/policy-details/v1", tok, params=[("ids", x) for x in csv(policy_ids)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/cspm_list_service_policy_settings.py b/integrations/crowdstrike/scripts/cspm_list_service_policy_settings.py new file mode 100644 index 0000000..2a5fc97 --- /dev/null +++ b/integrations/crowdstrike/scripts/cspm_list_service_policy_settings.py @@ -0,0 +1,58 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + policy_id = INPUTS.get("policy_id", "") + cloud_platform = INPUTS.get("cloud_platform", "aws") + service = INPUTS.get("service", "") + limit = INPUTS.get("limit", "") + + params = [("policy-id", policy_id), ("cloud-platform", cloud_platform)] + if service: + params.append(("service", service)) + if limit: + params.append(("limit", limit)) + + result = call("GET", "/settings/entities/policy/v1", tok, params=params) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/cspm_update_policy_settings.py b/integrations/crowdstrike/scripts/cspm_update_policy_settings.py new file mode 100644 index 0000000..69b5cdc --- /dev/null +++ b/integrations/crowdstrike/scripts/cspm_update_policy_settings.py @@ -0,0 +1,67 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + policy_id = INPUTS.get("policy_id", "") + account_id = INPUTS.get("account_id", "") + enabled = INPUTS.get("enabled", "true") + regions = INPUTS.get("regions", "") + severity = INPUTS.get("severity", "") + tag_excluded = INPUTS.get("tag_excluded", "") + + res = { + "policy_id": int(policy_id), + "enabled": (str(enabled).lower() == "true"), + } + if account_id: + res["account_id"] = account_id + if severity: + res["severity"] = severity + if regions: + res["regions"] = csv(regions) + if tag_excluded: + res["tag_excluded"] = (str(tag_excluded).lower() == "true") + + result = call("PATCH", "/settings/entities/policy/v1", tok, body={"resources": [res]}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/cve.py b/integrations/crowdstrike/scripts/cve.py new file mode 100644 index 0000000..37b287d --- /dev/null +++ b/integrations/crowdstrike/scripts/cve.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + cve = INPUTS.get("cve", "") + filt = "cve.id:['" + "','".join(csv(cve)) + "']" + result = call("GET", "/spotlight/combined/vulnerabilities/v1", tok, params=[("filter", filt), ("facet", "cve")]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/delete_case_tag.py b/integrations/crowdstrike/scripts/delete_case_tag.py new file mode 100644 index 0000000..0ec7066 --- /dev/null +++ b/integrations/crowdstrike/scripts/delete_case_tag.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + id_ = INPUTS.get("id", "") + tag = INPUTS.get("tag", "") + result = call("DELETE", "/cases/entities/case-tags/v1", tok, params=[("id", id_), ("tag", tag)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/delete_file.py b/integrations/crowdstrike/scripts/delete_file.py new file mode 100644 index 0000000..bec5d0a --- /dev/null +++ b/integrations/crowdstrike/scripts/delete_file.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + file_id = INPUTS.get("file_id", "") + result = call("DELETE", "/real-time-response/entities/put-files/v1", tok, + params=[("ids", file_id)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/delete_host_groups.py b/integrations/crowdstrike/scripts/delete_host_groups.py new file mode 100644 index 0000000..38ba15f --- /dev/null +++ b/integrations/crowdstrike/scripts/delete_host_groups.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_group_id = INPUTS.get("host_group_id", "") + result = call("DELETE", "/devices/entities/host-groups/v1", tok, params=[("ids", x) for x in csv(host_group_id)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/delete_ioa_exclusion.py b/integrations/crowdstrike/scripts/delete_ioa_exclusion.py new file mode 100644 index 0000000..cb3d028 --- /dev/null +++ b/integrations/crowdstrike/scripts/delete_ioa_exclusion.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + result = call("DELETE", "/policy/entities/ioa-exclusions/v1", tok, params=[("ids", x) for x in csv(ids)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/delete_ml_exclusion.py b/integrations/crowdstrike/scripts/delete_ml_exclusion.py new file mode 100644 index 0000000..e1bc166 --- /dev/null +++ b/integrations/crowdstrike/scripts/delete_ml_exclusion.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + result = call("DELETE", "/policy/entities/ml-exclusions/v1", tok, params=[("ids", x) for x in csv(ids)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/delete_script.py b/integrations/crowdstrike/scripts/delete_script.py new file mode 100644 index 0000000..b78f232 --- /dev/null +++ b/integrations/crowdstrike/scripts/delete_script.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + script_id = INPUTS.get("script_id", "") + result = call("DELETE", "/real-time-response/entities/scripts/v1", tok, + params=[("ids", script_id)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/device_ran_on.py b/integrations/crowdstrike/scripts/device_ran_on.py new file mode 100644 index 0000000..bd36843 --- /dev/null +++ b/integrations/crowdstrike/scripts/device_ran_on.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + type_ = INPUTS.get("type", "") + value = INPUTS.get("value", "") + result = call("GET", "/indicators/queries/devices/v1", tok, params=[("type", type_), ("value", value)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/endpoint.py b/integrations/crowdstrike/scripts/endpoint.py new file mode 100644 index 0000000..34547ad --- /dev/null +++ b/integrations/crowdstrike/scripts/endpoint.py @@ -0,0 +1,60 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + parts = [] + if INPUTS.get("id"): + parts.append("device_id:'" + INPUTS.get("id") + "'") + if INPUTS.get("ip"): + parts.append("local_ip:'" + INPUTS.get("ip") + "'") + if INPUTS.get("hostname"): + parts.append("hostname:'" + INPUTS.get("hostname") + "'") + filt = "+".join(parts) + query = call("GET", "/devices/queries/devices/v1", tok, params=[("filter", filt), ("limit", "50")]) + ids = query.get("resources", []) + if ids: + result = call("POST", "/devices/entities/devices/v2", tok, body={"ids": ids}) + else: + result = {"resources": []} + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/get_custom_ioc.py b/integrations/crowdstrike/scripts/get_custom_ioc.py new file mode 100644 index 0000000..d884ae3 --- /dev/null +++ b/integrations/crowdstrike/scripts/get_custom_ioc.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ioc_id = INPUTS.get("ioc_id", "") + result = call("GET", "/iocs/entities/indicators/v1", tok, params=[("ids", ioc_id)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/get_evidence_for_case.py b/integrations/crowdstrike/scripts/get_evidence_for_case.py new file mode 100644 index 0000000..600b0ae --- /dev/null +++ b/integrations/crowdstrike/scripts/get_evidence_for_case.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + id_ = INPUTS.get("id", "") + result = call("POST", "/cases/entities/cases/v2", tok, body={"ids": [id_]}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/get_file.py b/integrations/crowdstrike/scripts/get_file.py new file mode 100644 index 0000000..8a4ff6e --- /dev/null +++ b/integrations/crowdstrike/scripts/get_file.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + file_id = INPUTS.get("file_id", "") + result = call("GET", "/real-time-response/entities/put-files/v2", tok, + params=[("ids", x) for x in csv(file_id)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/get_ioarules.py b/integrations/crowdstrike/scripts/get_ioarules.py new file mode 100644 index 0000000..a0d9c3b --- /dev/null +++ b/integrations/crowdstrike/scripts/get_ioarules.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + rule_ids = INPUTS.get("rule_ids", "") + result = call("GET", "/ioarules/entities/rules/v1", tok, params=[("ids", x) for x in csv(rule_ids)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/get_script.py b/integrations/crowdstrike/scripts/get_script.py new file mode 100644 index 0000000..127ae49 --- /dev/null +++ b/integrations/crowdstrike/scripts/get_script.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + script_id = INPUTS.get("script_id", "") + result = call("GET", "/real-time-response/entities/scripts/v2", tok, + params=[("ids", x) for x in csv(script_id)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_case_summaries.py b/integrations/crowdstrike/scripts/list_case_summaries.py new file mode 100644 index 0000000..c9db037 --- /dev/null +++ b/integrations/crowdstrike/scripts/list_case_summaries.py @@ -0,0 +1,53 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids_input = INPUTS.get("ids", "") + if ids_input: + result = call("POST", "/cases/entities/cases/v2", tok, body={"ids": csv(ids_input)}) + else: + query = call("GET", "/cases/queries/cases/v1", tok, params=[("sort", "created_timestamp.asc"), ("limit", "100")]) + resources = query.get("resources", []) + result = call("POST", "/cases/entities/cases/v2", tok, body={"ids": resources}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_cnapp_alerts.py b/integrations/crowdstrike/scripts/list_cnapp_alerts.py new file mode 100644 index 0000000..f22d601 --- /dev/null +++ b/integrations/crowdstrike/scripts/list_cnapp_alerts.py @@ -0,0 +1,53 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + filter_ = INPUTS.get("filter", "") + + params = [("offset", "0"), ("limit", "100")] + if filter_: + params.append(("filter", filter_)) + + result = call("GET", "/container-security/combined/container-alerts/v1", tok, params=params) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_detection_summaries.py b/integrations/crowdstrike/scripts/list_detection_summaries.py new file mode 100644 index 0000000..89f9d13 --- /dev/null +++ b/integrations/crowdstrike/scripts/list_detection_summaries.py @@ -0,0 +1,55 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids_input = INPUTS.get("ids", "") + if ids_input: + comp = csv(ids_input) + else: + fetch_query = INPUTS.get("fetch_query", "") + params = [("filter", fetch_query)] if fetch_query else [] + query = call("GET", "/alerts/queries/alerts/v2", tok, params=params) + comp = query.get("resources", []) + result = call("POST", "/alerts/entities/alerts/v2", tok, body={"composite_ids": comp}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_files.py b/integrations/crowdstrike/scripts/list_files.py new file mode 100644 index 0000000..cf8098f --- /dev/null +++ b/integrations/crowdstrike/scripts/list_files.py @@ -0,0 +1,47 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + result = call("GET", "/real-time-response/entities/put-files/v2", tok) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_host_files.py b/integrations/crowdstrike/scripts/list_host_files.py new file mode 100644 index 0000000..2bb98c9 --- /dev/null +++ b/integrations/crowdstrike/scripts/list_host_files.py @@ -0,0 +1,54 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_id = INPUTS.get("host_id", "") + session_id = INPUTS.get("session_id", "") + if not session_id: + init = call("POST", "/real-time-response/entities/sessions/v1", tok, + body={"device_id": host_id}) + session_id = init.get("resources", [{}])[0].get("session_id", "") + result = call("GET", "/real-time-response/entities/file/v2", tok, + params=[("session_id", session_id)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_host_group_members.py b/integrations/crowdstrike/scripts/list_host_group_members.py new file mode 100644 index 0000000..9c922dc --- /dev/null +++ b/integrations/crowdstrike/scripts/list_host_group_members.py @@ -0,0 +1,63 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_group_id = INPUTS.get("host_group_id", "") + filter_ = INPUTS.get("filter", "") + limit = INPUTS.get("limit", "50") + offset = INPUTS.get("offset", "") + sort = INPUTS.get("sort", "") + params = [] + if host_group_id: + params.append(("id", host_group_id)) + if filter_: + params.append(("filter", filter_)) + if limit: + params.append(("limit", limit)) + if offset: + params.append(("offset", offset)) + if sort: + params.append(("sort", sort)) + result = call("GET", "/devices/combined/host-group-members/v1", tok, params=params if params else None) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_identity_entities.py b/integrations/crowdstrike/scripts/list_identity_entities.py new file mode 100644 index 0000000..d03481a --- /dev/null +++ b/integrations/crowdstrike/scripts/list_identity_entities.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + q = '{ entities(types: [%s], first: %s) { nodes { primaryDisplayName secondaryDisplayName riskScore riskScoreSeverity riskFactors { type severity } ... on UserEntity { emailAddresses } } } }' % (INPUTS.get("type", "USER"), str(INPUTS.get("limit", "50"))) + result = call("POST", "/identity-protection/combined/graphql/v1", tok, body={"query": q}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_quarantined_file.py b/integrations/crowdstrike/scripts/list_quarantined_file.py new file mode 100644 index 0000000..b4c149a --- /dev/null +++ b/integrations/crowdstrike/scripts/list_quarantined_file.py @@ -0,0 +1,67 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + filter_ = INPUTS.get("filter", "") + limit = INPUTS.get("limit", "50") + offset = INPUTS.get("offset", "") + + if ids: + q_ids = csv(ids) + else: + params = [] + if filter_: + params.append(("filter", filter_)) + params.append(("limit", limit)) + if offset: + params.append(("offset", offset)) + resp = call("GET", "/quarantine/queries/quarantined-files/v1", tok, params=params) + q_ids = resp.get("resources", []) + + if q_ids: + result = call("POST", "/quarantine/entities/quarantined-files/GET/v1", tok, body={"ids": q_ids}) + else: + result = {"resources": []} + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_scripts.py b/integrations/crowdstrike/scripts/list_scripts.py new file mode 100644 index 0000000..aa5afec --- /dev/null +++ b/integrations/crowdstrike/scripts/list_scripts.py @@ -0,0 +1,47 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + result = call("GET", "/real-time-response/entities/scripts/v2", tok) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_users.py b/integrations/crowdstrike/scripts/list_users.py new file mode 100644 index 0000000..3218960 --- /dev/null +++ b/integrations/crowdstrike/scripts/list_users.py @@ -0,0 +1,61 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + id_ = INPUTS.get("id", "") + filter_ = INPUTS.get("filter", "") + offset = INPUTS.get("offset", "0") + limit = INPUTS.get("limit", "50") + + if id_: + u_ids = csv(id_) + else: + params = [("offset", offset), ("limit", limit), ("sort", "uid")] + if filter_: + params.append(("filter", filter_)) + query_res = call("GET", "/user-management/queries/users/v1", tok, params=params) + u_ids = query_res.get("resources", []) + + result = call("POST", "/user-management/entities/users/GET/v1", tok, body={"ids": u_ids}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_workflow_definitions.py b/integrations/crowdstrike/scripts/list_workflow_definitions.py new file mode 100644 index 0000000..28aeeb1 --- /dev/null +++ b/integrations/crowdstrike/scripts/list_workflow_definitions.py @@ -0,0 +1,60 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + filter_ = INPUTS.get("filter", "") + offset = INPUTS.get("offset", "0") + limit = INPUTS.get("limit", "50") + sort = INPUTS.get("sort", "") + params = [] + if filter_: + params.append(("filter", filter_)) + if offset: + params.append(("offset", offset)) + if limit: + params.append(("limit", limit)) + if sort: + params.append(("sort", sort)) + result = call("GET", "/workflows/combined/definitions/v1", tok, params=params or None) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_workflow_execution_results.py b/integrations/crowdstrike/scripts/list_workflow_execution_results.py new file mode 100644 index 0000000..1bb348d --- /dev/null +++ b/integrations/crowdstrike/scripts/list_workflow_execution_results.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + params = [("ids", x) for x in csv(ids)] + result = call("GET", "/workflows/entities/execution-results/v1", tok, params=params or None) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/list_workflow_executions.py b/integrations/crowdstrike/scripts/list_workflow_executions.py new file mode 100644 index 0000000..8622374 --- /dev/null +++ b/integrations/crowdstrike/scripts/list_workflow_executions.py @@ -0,0 +1,60 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + filter_ = INPUTS.get("filter", "") + offset = INPUTS.get("offset", "0") + limit = INPUTS.get("limit", "50") + sort = INPUTS.get("sort", "") + params = [] + if filter_: + params.append(("filter", filter_)) + if offset: + params.append(("offset", offset)) + if limit: + params.append(("limit", limit)) + if sort: + params.append(("sort", sort)) + result = call("GET", "/workflows/combined/executions/v1", tok, params=params or None) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/ods_create_scan.py b/integrations/crowdstrike/scripts/ods_create_scan.py new file mode 100644 index 0000000..9eddc78 --- /dev/null +++ b/integrations/crowdstrike/scripts/ods_create_scan.py @@ -0,0 +1,68 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + hosts = INPUTS.get("hosts", "") + host_groups = INPUTS.get("host_groups", "") + file_paths = INPUTS.get("file_paths", "") + scan_inclusions = INPUTS.get("scan_inclusions", "") + description = INPUTS.get("description", "") + quarantine = INPUTS.get("quarantine", "") + + body = {} + if hosts: + body["hosts"] = csv(hosts) + if host_groups: + body["host_groups"] = csv(host_groups) + if file_paths: + body["file_paths"] = csv(file_paths) + if scan_inclusions: + body["scan_inclusions"] = csv(scan_inclusions) + if description: + body["description"] = description + if quarantine: + body["quarantine"] = (str(quarantine).lower() == "true") + + result = call("POST", "/ods/entities/scans/v1", tok, body=body) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/ods_create_scheduled_scan.py b/integrations/crowdstrike/scripts/ods_create_scheduled_scan.py new file mode 100644 index 0000000..950f0c8 --- /dev/null +++ b/integrations/crowdstrike/scripts/ods_create_scheduled_scan.py @@ -0,0 +1,77 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_groups = INPUTS.get("host_groups", "") + file_paths = INPUTS.get("file_paths", "") + scan_inclusions = INPUTS.get("scan_inclusions", "") + description = INPUTS.get("description", "") + schedule_start_timestamp = INPUTS.get("schedule_start_timestamp", "") + schedule_interval = INPUTS.get("schedule_interval", "") + + interval_map = { + "never": 0, + "daily": 1, + "weekly": 7, + "every other week": 14, + "every four weeks": 28, + "monthly": 30, + } + + body = { + "host_groups": csv(host_groups), + "schedule": { + "interval": interval_map.get(schedule_interval.lower(), 0), + "start_timestamp": schedule_start_timestamp, + }, + } + if file_paths: + body["file_paths"] = csv(file_paths) + if scan_inclusions: + body["scan_inclusions"] = csv(scan_inclusions) + if description: + body["description"] = description + + result = call("POST", "/ods/entities/scheduled-scans/v1", tok, body=body) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/ods_delete_scheduled_scan.py b/integrations/crowdstrike/scripts/ods_delete_scheduled_scan.py new file mode 100644 index 0000000..113fcc5 --- /dev/null +++ b/integrations/crowdstrike/scripts/ods_delete_scheduled_scan.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + result = call("DELETE", "/ods/entities/scheduled-scans/v1", tok, params=[("ids", x) for x in csv(ids)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/ods_query_malicious_files.py b/integrations/crowdstrike/scripts/ods_query_malicious_files.py new file mode 100644 index 0000000..3c5da27 --- /dev/null +++ b/integrations/crowdstrike/scripts/ods_query_malicious_files.py @@ -0,0 +1,67 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + file_ids = INPUTS.get("file_ids", "") + filter_ = INPUTS.get("filter", "") + limit = INPUTS.get("limit", "") + offset = INPUTS.get("offset", "") + + if file_ids: + result = call("GET", "/ods/entities/malicious-files/v1", tok, params=[("ids", x) for x in csv(file_ids)]) + else: + params = [] + if filter_: + params.append(("filter", filter_)) + if limit: + params.append(("limit", limit)) + if offset: + params.append(("offset", offset)) + query_res = call("GET", "/ods/queries/malicious-files/v1", tok, params=params or None) + resources = query_res.get("resources", []) + if resources: + result = call("GET", "/ods/entities/malicious-files/v1", tok, params=[("ids", x) for x in resources]) + else: + result = {"resources": []} + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/ods_query_scan.py b/integrations/crowdstrike/scripts/ods_query_scan.py new file mode 100644 index 0000000..d904cca --- /dev/null +++ b/integrations/crowdstrike/scripts/ods_query_scan.py @@ -0,0 +1,67 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + filter_ = INPUTS.get("filter", "") + limit = INPUTS.get("limit", "") + offset = INPUTS.get("offset", "") + + if ids: + result = call("GET", "/ods/entities/scans/v1", tok, params=[("ids", x) for x in csv(ids)]) + else: + params = [] + if filter_: + params.append(("filter", filter_)) + if limit: + params.append(("limit", limit)) + if offset: + params.append(("offset", offset)) + query_res = call("GET", "/ods/queries/scans/v1", tok, params=params or None) + resources = query_res.get("resources", []) + if resources: + result = call("GET", "/ods/entities/scans/v1", tok, params=[("ids", x) for x in resources]) + else: + result = {"resources": []} + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/ods_query_scan_host.py b/integrations/crowdstrike/scripts/ods_query_scan_host.py new file mode 100644 index 0000000..d8d6baf --- /dev/null +++ b/integrations/crowdstrike/scripts/ods_query_scan_host.py @@ -0,0 +1,67 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + filter_ = INPUTS.get("filter", "") + limit = INPUTS.get("limit", "") + offset = INPUTS.get("offset", "") + + if ids: + result = call("GET", "/ods/entities/scan-hosts/v1", tok, params=[("ids", x) for x in csv(ids)]) + else: + params = [] + if filter_: + params.append(("filter", filter_)) + if limit: + params.append(("limit", limit)) + if offset: + params.append(("offset", offset)) + query_res = call("GET", "/ods/queries/scan-hosts/v1", tok, params=params or None) + resources = query_res.get("resources", []) + if resources: + result = call("GET", "/ods/entities/scan-hosts/v1", tok, params=[("ids", x) for x in resources]) + else: + result = {"resources": []} + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/ods_query_scheduled_scan.py b/integrations/crowdstrike/scripts/ods_query_scheduled_scan.py new file mode 100644 index 0000000..6e0b471 --- /dev/null +++ b/integrations/crowdstrike/scripts/ods_query_scheduled_scan.py @@ -0,0 +1,67 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + filter_ = INPUTS.get("filter", "") + limit = INPUTS.get("limit", "") + offset = INPUTS.get("offset", "") + + if ids: + result = call("GET", "/ods/entities/scheduled-scans/v1", tok, params=[("ids", x) for x in csv(ids)]) + else: + params = [] + if filter_: + params.append(("filter", filter_)) + if limit: + params.append(("limit", limit)) + if offset: + params.append(("offset", offset)) + query_res = call("GET", "/ods/queries/scheduled-scans/v1", tok, params=params or None) + resources = query_res.get("resources", []) + if resources: + result = call("GET", "/ods/entities/scheduled-scans/v1", tok, params=[("ids", x) for x in resources]) + else: + result = {"resources": []} + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/process_details.py b/integrations/crowdstrike/scripts/process_details.py new file mode 100644 index 0000000..216a24c --- /dev/null +++ b/integrations/crowdstrike/scripts/process_details.py @@ -0,0 +1,48 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + result = call("GET", "/processes/entities/processes/v1", tok, params=[("ids", x) for x in csv(ids)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/processes_ran_on.py b/integrations/crowdstrike/scripts/processes_ran_on.py new file mode 100644 index 0000000..37a2667 --- /dev/null +++ b/integrations/crowdstrike/scripts/processes_ran_on.py @@ -0,0 +1,50 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + type_ = INPUTS.get("type", "") + value = INPUTS.get("value", "") + device_id = INPUTS.get("device_id", "") + result = call("GET", "/indicators/queries/processes/v1", tok, params=[("type", type_), ("value", value), ("device_id", device_id)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/refresh_session.py b/integrations/crowdstrike/scripts/refresh_session.py new file mode 100644 index 0000000..df5cf8b --- /dev/null +++ b/integrations/crowdstrike/scripts/refresh_session.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_id = INPUTS.get("host_id", "") + result = call("POST", "/real-time-response/entities/refresh-session/v1", tok, + body={"device_id": host_id}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/remove_host_group_members.py b/integrations/crowdstrike/scripts/remove_host_group_members.py new file mode 100644 index 0000000..5bbb709 --- /dev/null +++ b/integrations/crowdstrike/scripts/remove_host_group_members.py @@ -0,0 +1,58 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_group_id = INPUTS.get("host_group_id", "") + host_ids = INPUTS.get("host_ids", "") + result = call( + "POST", + "/devices/entities/host-group-actions/v1", + tok, + params=[("action_name", "remove-hosts")], + body={ + "action_parameters": [{"name": "filter", "value": "(device_id:" + str(csv(host_ids)) + ")"}], + "ids": [host_group_id], + }, + ) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/resolve_case.py b/integrations/crowdstrike/scripts/resolve_case.py new file mode 100644 index 0000000..ec2121a --- /dev/null +++ b/integrations/crowdstrike/scripts/resolve_case.py @@ -0,0 +1,59 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + id_ = INPUTS.get("id", "") + fields = {} + if INPUTS.get("status"): + fields["status"] = INPUTS.get("status") + if INPUTS.get("name"): + fields["name"] = INPUTS.get("name") + if INPUTS.get("description"): + fields["description"] = INPUTS.get("description") + if INPUTS.get("assigned_to_uuid"): + fields["assigned_to_user_uuid"] = INPUTS.get("assigned_to_uuid") + if INPUTS.get("severity"): + fields["severity"] = int(INPUTS.get("severity")) + result = call("PATCH", "/cases/entities/cases/v2", tok, body={"id": id_, "fields": fields}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/resolve_identity_detection.py b/integrations/crowdstrike/scripts/resolve_identity_detection.py new file mode 100644 index 0000000..ecd8ab6 --- /dev/null +++ b/integrations/crowdstrike/scripts/resolve_identity_detection.py @@ -0,0 +1,72 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + update_status = INPUTS.get("update_status", "") + assign_to_uuid = INPUTS.get("assign_to_uuid", "") + append_comment = INPUTS.get("append_comment", "") + add_tag = INPUTS.get("add_tag", "") + remove_tag = INPUTS.get("remove_tag", "") + unassign = INPUTS.get("unassign", "") + show_in_ui = INPUTS.get("show_in_ui", "") + + action_parameters = [] + for name, value in [ + ("update_status", update_status), + ("assign_to_uuid", assign_to_uuid), + ("append_comment", append_comment), + ("add_tag", add_tag), + ("remove_tag", remove_tag), + ("unassign", unassign), + ("show_in_ui", show_in_ui), + ]: + if value: + action_parameters.append({"name": name, "value": value}) + + result = call("PATCH", "/alerts/entities/alerts/v3", tok, body={ + "action_parameters": action_parameters, + "composite_ids": csv(ids), + }) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/resolve_mobile_detection.py b/integrations/crowdstrike/scripts/resolve_mobile_detection.py new file mode 100644 index 0000000..ecd8ab6 --- /dev/null +++ b/integrations/crowdstrike/scripts/resolve_mobile_detection.py @@ -0,0 +1,72 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + update_status = INPUTS.get("update_status", "") + assign_to_uuid = INPUTS.get("assign_to_uuid", "") + append_comment = INPUTS.get("append_comment", "") + add_tag = INPUTS.get("add_tag", "") + remove_tag = INPUTS.get("remove_tag", "") + unassign = INPUTS.get("unassign", "") + show_in_ui = INPUTS.get("show_in_ui", "") + + action_parameters = [] + for name, value in [ + ("update_status", update_status), + ("assign_to_uuid", assign_to_uuid), + ("append_comment", append_comment), + ("add_tag", add_tag), + ("remove_tag", remove_tag), + ("unassign", unassign), + ("show_in_ui", show_in_ui), + ]: + if value: + action_parameters.append({"name": name, "value": value}) + + result = call("PATCH", "/alerts/entities/alerts/v3", tok, body={ + "action_parameters": action_parameters, + "composite_ids": csv(ids), + }) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/rtr_list_network_stats.py b/integrations/crowdstrike/scripts/rtr_list_network_stats.py new file mode 100644 index 0000000..d5b15d2 --- /dev/null +++ b/integrations/crowdstrike/scripts/rtr_list_network_stats.py @@ -0,0 +1,57 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_id = INPUTS.get("host_id", "") + qoff = str(INPUTS.get("queue_offline", "false")).lower() == "true" + timeout = INPUTS.get("timeout", "30") + + init = call("POST", "/real-time-response/combined/batch-init-session/v1", tok, + body={"host_ids": [host_id], "queue_offline": qoff}) + batch_id = init.get("batch_id") + + result = call("POST", "/real-time-response/combined/batch-active-responder-command/v1", tok, + params=[("timeout", timeout)], + body={"base_command": "netstat", "batch_id": batch_id, "command_string": "netstat"}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/rtr_list_scheduled_tasks.py b/integrations/crowdstrike/scripts/rtr_list_scheduled_tasks.py new file mode 100644 index 0000000..7103ede --- /dev/null +++ b/integrations/crowdstrike/scripts/rtr_list_scheduled_tasks.py @@ -0,0 +1,59 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_ids = INPUTS.get("host_ids", "") + qoff = str(INPUTS.get("queue_offline", "false")).lower() == "true" + timeout = INPUTS.get("timeout", "30") + + init = call("POST", "/real-time-response/combined/batch-init-session/v1", tok, + body={"host_ids": csv(host_ids), "queue_offline": qoff}) + batch_id = init.get("batch_id") + + command_string = "runscript -Raw=" + chr(96) * 3 + "schtasks /query /fo LIST /v" + chr(96) * 3 + + result = call("POST", "/real-time-response/combined/batch-admin-command/v1", tok, + params=[("timeout", timeout)], + body={"base_command": "runscript", "batch_id": batch_id, "command_string": command_string}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/rtr_remove_file.py b/integrations/crowdstrike/scripts/rtr_remove_file.py new file mode 100644 index 0000000..014962a --- /dev/null +++ b/integrations/crowdstrike/scripts/rtr_remove_file.py @@ -0,0 +1,64 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_ids = INPUTS.get("host_ids", "") + file_path = INPUTS.get("file_path", "") + os_type = INPUTS.get("os", "Windows") + qoff = str(INPUTS.get("queue_offline", "false")).lower() == "true" + timeout = INPUTS.get("timeout", "30") + + init = call("POST", "/real-time-response/combined/batch-init-session/v1", tok, + body={"host_ids": csv(host_ids), "queue_offline": qoff}) + batch_id = init.get("batch_id") + + if os_type == "Windows": + full = "rm '" + file_path + "' --force" + else: + full = "rm '" + file_path + "' -r -d" + + result = call("POST", "/real-time-response/combined/batch-active-responder-command/v1", tok, + params=[("timeout", timeout)], + body={"base_command": "rm", "batch_id": batch_id, "command_string": full}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/search_custom_iocs.py b/integrations/crowdstrike/scripts/search_custom_iocs.py new file mode 100644 index 0000000..f8f786f --- /dev/null +++ b/integrations/crowdstrike/scripts/search_custom_iocs.py @@ -0,0 +1,76 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + types = INPUTS.get("types", "") + values = INPUTS.get("values", "") + sources = INPUTS.get("sources", "") + expiration = INPUTS.get("expiration", "") + limit = INPUTS.get("limit", "50") + sort = INPUTS.get("sort", "") + offset = INPUTS.get("offset", "") + + filter_parts = [] + if types: + filter_parts.append("type:" + types) + if values: + filter_parts.append("value:" + values) + if sources: + filter_parts.append("source:" + sources) + if expiration: + filter_parts.append("expiration:\"" + expiration + "\"") + filt = "+".join(filter_parts) + + params = [] + if filt: + params.append(("filter", filt)) + if sort: + params.append(("sort", sort)) + if offset: + params.append(("offset", offset)) + if limit: + params.append(("limit", limit)) + + result = call("GET", "/iocs/combined/indicator/v1", tok, params=params if params else None) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/search_ioa_exclusion.py b/integrations/crowdstrike/scripts/search_ioa_exclusion.py new file mode 100644 index 0000000..e4a89df --- /dev/null +++ b/integrations/crowdstrike/scripts/search_ioa_exclusion.py @@ -0,0 +1,77 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + name = INPUTS.get("name", "") + filter_ = INPUTS.get("filter", "") + limit = INPUTS.get("limit", "") + offset = INPUTS.get("offset", "") + + if ids: + ex_ids = csv(ids) + elif name: + params = [("filter", "name:~'" + name + "'")] + if limit: + params.append(("limit", limit)) + if offset: + params.append(("offset", offset)) + resp = call("GET", "/policy/queries/ioa-exclusions/v1", tok, params=params) + ex_ids = resp.get("resources", []) + else: + params = [] + if filter_: + params.append(("filter", filter_)) + if limit: + params.append(("limit", limit)) + if offset: + params.append(("offset", offset)) + resp = call("GET", "/policy/queries/ioa-exclusions/v1", tok, params=params if params else None) + ex_ids = resp.get("resources", []) + + if ex_ids: + result = call("GET", "/policy/entities/ioa-exclusions/v1", tok, params=[("ids", x) for x in ex_ids]) + else: + result = {"resources": []} + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/search_ml_exclusion.py b/integrations/crowdstrike/scripts/search_ml_exclusion.py new file mode 100644 index 0000000..748786a --- /dev/null +++ b/integrations/crowdstrike/scripts/search_ml_exclusion.py @@ -0,0 +1,82 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + value = INPUTS.get("value", "") + filter_ = INPUTS.get("filter", "") + limit = INPUTS.get("limit", "") + offset = INPUTS.get("offset", "") + sort = INPUTS.get("sort", "") + + if ids: + ex_ids = csv(ids) + elif value: + params = [("filter", "value:'" + value + "'")] + if limit: + params.append(("limit", limit)) + if offset: + params.append(("offset", offset)) + if sort: + params.append(("sort", sort)) + resp = call("GET", "/policy/queries/ml-exclusions/v1", tok, params=params) + ex_ids = resp.get("resources", []) + else: + params = [] + if filter_: + params.append(("filter", filter_)) + if limit: + params.append(("limit", limit)) + if offset: + params.append(("offset", offset)) + if sort: + params.append(("sort", sort)) + resp = call("GET", "/policy/queries/ml-exclusions/v1", tok, params=params if params else None) + ex_ids = resp.get("resources", []) + + if ex_ids: + result = call("GET", "/policy/entities/ml-exclusions/v1", tok, params=[("ids", x) for x in ex_ids]) + else: + result = {"resources": []} + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/spotlight_list_host_by_vulnerability.py b/integrations/crowdstrike/scripts/spotlight_list_host_by_vulnerability.py new file mode 100644 index 0000000..8aad787 --- /dev/null +++ b/integrations/crowdstrike/scripts/spotlight_list_host_by_vulnerability.py @@ -0,0 +1,50 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + cve_ids = INPUTS.get("cve_ids", "") + limit = INPUTS.get("limit", "50") + filt = "cve.id:['" + "','".join(csv(cve_ids)) + "']" + result = call("GET", "/spotlight/combined/vulnerabilities/v1", tok, params=[("filter", filt), ("facet", "host_info"), ("limit", limit)]) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/update_custom_ioc.py b/integrations/crowdstrike/scripts/update_custom_ioc.py new file mode 100644 index 0000000..746922a --- /dev/null +++ b/integrations/crowdstrike/scripts/update_custom_ioc.py @@ -0,0 +1,74 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ioc_id = INPUTS.get("ioc_id", "") + ind = {"id": ioc_id} + + action = INPUTS.get("action", "") + if action: + ind["action"] = action + + platforms = INPUTS.get("platforms", "") + if platforms: + ind["platforms"] = csv(platforms) + + severity = INPUTS.get("severity", "") + if severity: + ind["severity"] = severity + + source = INPUTS.get("source", "") + if source: + ind["source"] = source + + description = INPUTS.get("description", "") + if description: + ind["description"] = description + + expiration = INPUTS.get("expiration", "") + if expiration: + ind["expiration"] = expiration + + result = call("PATCH", "/iocs/entities/indicators/v1", tok, body={"indicators": [ind]}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/update_host_group.py b/integrations/crowdstrike/scripts/update_host_group.py new file mode 100644 index 0000000..4d52f58 --- /dev/null +++ b/integrations/crowdstrike/scripts/update_host_group.py @@ -0,0 +1,58 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + host_group_id = INPUTS.get("host_group_id", "") + name = INPUTS.get("name", "") + description = INPUTS.get("description", "") + assignment_rule = INPUTS.get("assignment_rule", "") + res = {"id": host_group_id} + if name: + res["name"] = name + if description: + res["description"] = description + if assignment_rule: + res["assignment_rule"] = assignment_rule + result = call("PATCH", "/devices/entities/host-groups/v1", tok, body={"resources": [res]}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/update_ioa_exclusion.py b/integrations/crowdstrike/scripts/update_ioa_exclusion.py new file mode 100644 index 0000000..9628e8f --- /dev/null +++ b/integrations/crowdstrike/scripts/update_ioa_exclusion.py @@ -0,0 +1,77 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + body = {"id": INPUTS.get("id", "")} + exclusion_name = INPUTS.get("exclusion_name", "") + pattern_id = INPUTS.get("pattern_id", "") + pattern_name = INPUTS.get("pattern_name", "") + cl_regex = INPUTS.get("cl_regex", "") + ifn_regex = INPUTS.get("ifn_regex", "") + comment = INPUTS.get("comment", "") + description = INPUTS.get("description", "") + groups = INPUTS.get("groups", "") + detection_json = INPUTS.get("detection_json", "") + + if exclusion_name: + body["name"] = exclusion_name + if pattern_id: + body["pattern_id"] = pattern_id + if pattern_name: + body["pattern_name"] = pattern_name + if cl_regex: + body["cl_regex"] = cl_regex + if ifn_regex: + body["ifn_regex"] = ifn_regex + if comment: + body["comment"] = comment + if description: + body["description"] = description + if groups: + body["groups"] = csv(groups) + if detection_json: + body["detection_json"] = detection_json + + result = call("PATCH", "/policy/entities/ioa-exclusions/v1", tok, body=body) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/update_ml_exclusion.py b/integrations/crowdstrike/scripts/update_ml_exclusion.py new file mode 100644 index 0000000..06fe6c7 --- /dev/null +++ b/integrations/crowdstrike/scripts/update_ml_exclusion.py @@ -0,0 +1,57 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + body = {"id": INPUTS.get("id", "")} + value = INPUTS.get("value", "") + comment = INPUTS.get("comment", "") + groups = INPUTS.get("groups", "") + if value: + body["value"] = value + if comment: + body["comment"] = comment + if groups: + body["groups"] = csv(groups) + result = call("PATCH", "/policy/entities/ml-exclusions/v1", tok, body=body) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/upload_script.py b/integrations/crowdstrike/scripts/upload_script.py new file mode 100644 index 0000000..2482494 --- /dev/null +++ b/integrations/crowdstrike/scripts/upload_script.py @@ -0,0 +1,76 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + name = INPUTS.get("name", "") + permission_type = INPUTS.get("permission_type", "private") + content = INPUTS.get("content", "") + + boundary = "----RiposteBoundary7a3f91e2" + body_parts = [] + + for field_name, field_value in [("name", name), ("permission_type", permission_type), ("content", content)]: + body_parts.append( + "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + field_name + "\"\r\n\r\n" + + field_value + "\r\n" + ) + body_parts.append("--" + boundary + "--\r\n") + + body_bytes = "".join(body_parts).encode("utf-8") + headers = { + "Authorization": "Bearer " + tok, + "Accept": "application/json", + "Content-Type": "multipart/form-data; boundary=" + boundary, + } + req = urllib.request.Request( + BASE + "/real-time-response/entities/scripts/v1", + data=body_bytes, + headers=headers, + method="POST", + ) + with urllib.request.urlopen(req, timeout=90) as r: + raw = r.read() + result = json.loads(raw) if raw else {} + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/workflow_execute.py b/integrations/crowdstrike/scripts/workflow_execute.py new file mode 100644 index 0000000..7ea01e0 --- /dev/null +++ b/integrations/crowdstrike/scripts/workflow_execute.py @@ -0,0 +1,61 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + definition_id = INPUTS.get("definition_id", "") + name = INPUTS.get("name", "") + key = INPUTS.get("key", "") + source_event_url = INPUTS.get("source_event_url", "") + execution_cid = INPUTS.get("execution_cid", "") + params = [("definition_id", x) for x in csv(definition_id)] + if name: + params.append(("name", name)) + params += [("execution_cid", x) for x in csv(execution_cid)] + if key: + params.append(("key", key)) + if source_event_url: + params.append(("source_event_url", source_event_url)) + json_body = json.loads(INPUTS.get("body") or "{}") + result = call("POST", "/workflows/entities/execute/v1", tok, params=params or None, body=json_body) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) diff --git a/integrations/crowdstrike/scripts/workflow_execution_action.py b/integrations/crowdstrike/scripts/workflow_execution_action.py new file mode 100644 index 0000000..130def8 --- /dev/null +++ b/integrations/crowdstrike/scripts/workflow_execution_action.py @@ -0,0 +1,50 @@ +import json, os, sys, urllib.request, urllib.parse, urllib.error + +SECRETS = json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) +INPUTS = json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) +BASE = SECRETS.get("base_url", "https://api.crowdstrike.com").rstrip("/") + + +def token(): + data = urllib.parse.urlencode({"client_id": SECRETS.get("client_id", ""), "client_secret": SECRETS.get("client_secret", "")}).encode() + req = urllib.request.Request(BASE + "/oauth2/token", data=data, headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, method="POST") + with urllib.request.urlopen(req, timeout=30) as r: + return json.loads(r.read()).get("access_token", "") + + +def call(method, path, tok, params=None, body=None): + url = BASE + path + if params: + # params is a list of (key, value) tuples to allow repeated keys (e.g. ids) + url += "?" + urllib.parse.urlencode(params) + data = json.dumps(body).encode() if body is not None else None + headers = {"Authorization": "Bearer " + tok, "Accept": "application/json"} + if data is not None: + headers["Content-Type"] = "application/json" + 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 csv(v): + return [x.strip() for x in str(v or "").split(",") if x.strip()] + + +def main(): + tok = token() + ids = INPUTS.get("ids", "") + action_name = INPUTS.get("action_name", "") + params = [("action_name", action_name)] + result = call("POST", "/workflows/entities/execution-actions/v1", tok, params=params, body={"ids": csv(ids)}) + print(json.dumps(result)) + + +try: + main() +except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) +except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1)