SysdigResponseActions
Uses the Sysdig agent to respond to malicious activity by triggering different actions at the host or container level, such as killing a container, quarantining a file, or performing a system capture.
Data Enrichment & Threat Intelligence · Sysdig Response Actions
Details
| ID | SysdigResponseActions |
|---|---|
| Provider | Sysdig |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.10.0 |
| To Version | 99.99.99 |
| Docker Image | demisto/python3:3.12.13.10116658 |
README
This is an integration that will use Sysdig agent to respond to malicious activity by triggering different actions at the host or container level like killing a container, quarantine a file or perform a system capture
This integration was integrated and tested with Host shield 13.9.1 of the Sysdig Agent and ResponseActions version 0.1.0
This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.
Configure Sysdig Response Actions in Cortex
| Parameter | Required |
|---|---|
| Your server URL | True |
| API Key | True |
| Trust any certificate (not secure) | False |
| Use system proxy settings | False |
| Classifier | False |
| Incident type (if classifier doesn’t exist) | False |
| Mapper (incoming) | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
execute-response-action
Executes response actions through the Sysdig API. Each action type requires a specific set of parameters.
Base Command
execute-response-action
Input
| Argument Name | Description | Required |
|---|---|---|
| actionType | The action type to perform. Required parameters per action: - KILL_PROCESS: host_id, process_id (startTime defaults to -1) - KILL_CONTAINER: host_id, container_id - STOP_CONTAINER: host_id, container_id - PAUSE_CONTAINER: host_id, container_id - UNPAUSE_CONTAINER: host_id, container_id - START_CONTAINER: host_id, container_id - FILE_QUARANTINE: host_id, path_absolute (optional: container_id) - FILE_ACQUIRE: host_id, path_absolute (optional: container_id) - FILE_UNQUARANTINE: host_id, path_absolute, quarantined_file_path (optional: container_id) - DELETE_POD: k8s_cluster_name, k8s_namespace_name, k8s_pod_name - ROLLOUT_RESTART: k8s_cluster_name, k8s_namespace_name, k8s_workload_type, k8s_workload_name - ISOLATE_NETWORK: k8s_cluster_name, k8s_namespace_name, k8s_workload_type, k8s_workload_name (optional: network_protocol, network_port, network_cidr, network_direction) - DELETE_NETWORK_POLICY: k8s_cluster_name, k8s_namespace_name, network_policy_name - GET_LOGS: k8s_cluster_name, k8s_namespace_name (optional: k8s_workload_type, k8s_workload_name, k8s_pod_name, k8s_container_name, previous, all_containers) - KUBERNETES_VOLUME_SNAPSHOT: k8s_cluster_name, k8s_namespace_name (optional: k8s_pvc_name, k8s_workload_type, k8s_workload_name) - KUBERNETES_DELETE_VOLUME_SNAPSHOT: k8s_cluster_name, k8s_namespace_name, k8s_pvc_name, k8s_volume_snapshot_name - CAPTURE: host_id, capture_storage_config_id, capture_duration_ns, capture_past_duration_ns (optional: capture_filters, capture_max_size, container_id, capture_token) - IAM_QUARANTINE: cloud_provider, cloud_account_id (optional: ct_user_arn, ct_user_identity_type, ct_user) - IAM_UNQUARANTINE: cloud_provider, cloud_account_id, iam_policy_name, ct_user_identity_type, ct_user - MAKE_PRIVATE_CLOUD_RESOURCE: cloud_provider, cloud_account_id, cloud_resource_type, cloud_resource_name (optional: cloud_region) - UNDO_MAKE_PRIVATE_CLOUD_RESOURCE: cloud_provider, cloud_account_id, cloud_resource_type, cloud_resource_name, previous_public_access_settings (optional: cloud_region) - CLOUD_VOLUME_SNAPSHOT: cloud_provider, cloud_account_id, cloud_region, aws_instance_id - UNDO_CLOUD_VOLUME_SNAPSHOT: cloud_provider, cloud_account_id, cloud_region, snapshot_ids, aws_instance_id - FETCH_CLOUD_LOGS: cloud_provider, cloud_account_id, cloud_region, from_timestamp, to_timestamp (optional: ct_original_user, ct_name, ct_src). Possible values are: KILL_PROCESS, KILL_CONTAINER, STOP_CONTAINER, PAUSE_CONTAINER, UNPAUSE_CONTAINER, START_CONTAINER, FILE_QUARANTINE, FILE_ACQUIRE, FILE_UNQUARANTINE, DELETE_POD, ROLLOUT_RESTART, ISOLATE_NETWORK, DELETE_NETWORK_POLICY, GET_LOGS, KUBERNETES_VOLUME_SNAPSHOT, KUBERNETES_DELETE_VOLUME_SNAPSHOT, CAPTURE, IAM_QUARANTINE, IAM_UNQUARANTINE, MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE, CLOUD_VOLUME_SNAPSHOT, UNDO_CLOUD_VOLUME_SNAPSHOT, FETCH_CLOUD_LOGS. |
Required |
| callerId | The unique caller identifier for the audit trail. | Required |
| host_id | The host ID where the agent runs. Required for: KILL_PROCESS, KILL_CONTAINER, STOP_CONTAINER, PAUSE_CONTAINER, UNPAUSE_CONTAINER, START_CONTAINER, FILE_QUARANTINE, FILE_ACQUIRE, FILE_UNQUARANTINE, CAPTURE. | Optional |
| container_id | The container ID. Required for: KILL_CONTAINER, STOP_CONTAINER, PAUSE_CONTAINER, UNPAUSE_CONTAINER, START_CONTAINER. Optional for: FILE_QUARANTINE, FILE_ACQUIRE, FILE_UNQUARANTINE, CAPTURE. | Optional |
| process_id | The process ID to kill. Required for: KILL_PROCESS. | Optional |
| path_absolute | The absolute file path. Required for: FILE_QUARANTINE, FILE_ACQUIRE, FILE_UNQUARANTINE. | Optional |
| quarantined_file_path | The path of the quarantined file. Required for: FILE_UNQUARANTINE. | Optional |
| k8s_cluster_name | The Kubernetes cluster name. Required for: DELETE_POD, ROLLOUT_RESTART, ISOLATE_NETWORK, DELETE_NETWORK_POLICY, GET_LOGS, KUBERNETES_VOLUME_SNAPSHOT, KUBERNETES_DELETE_VOLUME_SNAPSHOT. | Optional |
| k8s_namespace_name | The Kubernetes namespace. Required for: DELETE_POD, ROLLOUT_RESTART, ISOLATE_NETWORK, DELETE_NETWORK_POLICY, GET_LOGS, KUBERNETES_VOLUME_SNAPSHOT, KUBERNETES_DELETE_VOLUME_SNAPSHOT. | Optional |
| k8s_pod_name | The Kubernetes pod name. Required for: DELETE_POD. Optional for: GET_LOGS. | Optional |
| k8s_workload_type | The Kubernetes workload type (for example, Deployment, StatefulSet, DaemonSet). Required for: ROLLOUT_RESTART, ISOLATE_NETWORK. Optional for: GET_LOGS, KUBERNETES_VOLUME_SNAPSHOT. | Optional |
| k8s_workload_name | The Kubernetes workload name. Required for: ROLLOUT_RESTART, ISOLATE_NETWORK. Optional for: GET_LOGS, KUBERNETES_VOLUME_SNAPSHOT. | Optional |
| k8s_pvc_name | The PVC name. Optional for: KUBERNETES_VOLUME_SNAPSHOT. Required for: KUBERNETES_DELETE_VOLUME_SNAPSHOT. | Optional |
| k8s_volume_snapshot_name | The volume snapshot name. Required for: KUBERNETES_DELETE_VOLUME_SNAPSHOT. | Optional |
| k8s_container_name | The container name for log retrieval. Optional for: GET_LOGS. | Optional |
| network_policy_name | The network policy name. Required for: DELETE_NETWORK_POLICY. | Optional |
| network_protocol | The network protocol (TCP, UDP). Optional for: ISOLATE_NETWORK. | Optional |
| network_port | The port number. Optional for: ISOLATE_NETWORK. | Optional |
| network_cidr | The CIDR range. Optional for: ISOLATE_NETWORK. | Optional |
| network_direction | The traffic direction (ingress, egress). Optional for: ISOLATE_NETWORK. | Optional |
| previous | Whether to retrieve logs from the previous container instance. Optional for: GET_LOGS. | Optional |
| all_containers | Whether to retrieve logs from all containers. Optional for: GET_LOGS. | Optional |
| capture_storage_config_id | The remote storage configuration ID. Required for: CAPTURE. | Optional |
| capture_duration_ns | The capture duration in nanoseconds. Required for: CAPTURE. | Optional |
| capture_past_duration_ns | The amount of time to capture retroactively, in nanoseconds. Required for: CAPTURE. | Optional |
| capture_filters | The syscall filter expression. Optional for: CAPTURE. | Optional |
| capture_max_size | The maximum capture file size in bytes (0 = no limit). Optional for: CAPTURE. | Optional |
| capture_token | The base name of the capture file. Optional for: CAPTURE. | Optional |
| cloud_provider | The cloud provider name (for example, aws). Required for: IAM_QUARANTINE, IAM_UNQUARANTINE, MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE, CLOUD_VOLUME_SNAPSHOT, UNDO_CLOUD_VOLUME_SNAPSHOT, FETCH_CLOUD_LOGS. | Optional |
| cloud_account_id | The cloud account ID. Required for: IAM_QUARANTINE, IAM_UNQUARANTINE, MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE, CLOUD_VOLUME_SNAPSHOT, UNDO_CLOUD_VOLUME_SNAPSHOT, FETCH_CLOUD_LOGS. | Optional |
| cloud_region | The cloud region. Required for: CLOUD_VOLUME_SNAPSHOT, UNDO_CLOUD_VOLUME_SNAPSHOT, FETCH_CLOUD_LOGS. Optional for: MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE. | Optional |
| ct_user_arn | The AWS identity ARN. Optional for: IAM_QUARANTINE. | Optional |
| ct_user_identity_type | The AWS identity type (IAMUser, Role, AssumedRole). Required for: IAM_UNQUARANTINE. Optional for: IAM_QUARANTINE. | Optional |
| ct_user | The AWS identity name. Required for: IAM_UNQUARANTINE. Optional for: IAM_QUARANTINE. | Optional |
| ct_original_user | The CloudTrail original user. Optional for: FETCH_CLOUD_LOGS. | Optional |
| ct_name | The CloudTrail event name. Optional for: FETCH_CLOUD_LOGS. | Optional |
| ct_src | The CloudTrail source. Optional for: FETCH_CLOUD_LOGS. | Optional |
| iam_policy_name | The IAM policy name to remove. Required for: IAM_UNQUARANTINE. | Optional |
| cloud_resource_type | The cloud resource type (rds, s3). Required for: MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE. | Optional |
| cloud_resource_name | The cloud resource name. Required for: MAKE_PRIVATE_CLOUD_RESOURCE, UNDO_MAKE_PRIVATE_CLOUD_RESOURCE. | Optional |
| previous_public_access_settings | The previous public access settings. Required for: UNDO_MAKE_PRIVATE_CLOUD_RESOURCE. | Optional |
| aws_instance_id | The AWS instance ID. Required for: CLOUD_VOLUME_SNAPSHOT, UNDO_CLOUD_VOLUME_SNAPSHOT. | Optional |
| snapshot_ids | The comma-separated snapshot IDs. Required for: UNDO_CLOUD_VOLUME_SNAPSHOT. | Optional |
| from_timestamp | The start timestamp for the log search. Required for: FETCH_CLOUD_LOGS. | Optional |
| to_timestamp | The end timestamp for the log search. Required for: FETCH_CLOUD_LOGS. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| execute_response_action.Output | Dict | The output of the response-actions API. |
create-system-capture
Triggers a system capture, recording all system calls at the host level.
Base Command
create-system-capture
Input
| Argument Name | Description | Required |
|---|---|---|
| container_id | The container ID to apply the action. Example “container.id”: “123456789123”. | Required |
| host_name | The host name. Example “ip-1-1-1-1.us-west-1.compute.internal”. | Required |
| capture_name | The capture name. | Required |
| agent_id | The agent ID. | Required |
| customer_id | The customer ID. | Required |
| machine_id | The machine ID/MAC. Example “01:aa:02:bb:03:cc”. | Required |
| scan_duration | The capture duration in seconds. | Optional |
| scap_filter | The filter for the scope of the capture to take. Example: (proc.name=ncat or proc.name=vi). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| create_system_capture.Output | Dict | The output of the created system capture. |
get-capture-file
Gets a system capture based on the capture ID.
Base Command
get-capture-file
Input
| Argument Name | Description | Required |
|---|---|---|
| capture_id | The system capture ID. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| get_capture_file.Output | Dict | The output of the downloaded system capture. |
get-action-execution
Gets the status and information of a triggered action execution.
Base Command
get-action-execution
Input
| Argument Name | Description | Required |
|---|---|---|
| action_execution_id | The action execution ID. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| get_action_execution.Output | Dict | The output of the action execution info. |
sysdig-agent-info-get
Resolves a host MAC address to its Sysdig agent details (agent ID, customer ID, hostname). The result is cached per MAC address.
Base Command
sysdig-agent-info-get
Input
| Argument Name | Description | Required |
|---|---|---|
| machine_id | The MAC address (machineId) of the host to look up. | Required |
| force_refresh | Whether to force a refresh of the cached agent info from the Sysdig API. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Sysdig.Agent.agentId | String | The Sysdig agent ID. |
| Sysdig.Agent.customerId | String | The Sysdig customer ID. |
| Sysdig.Agent.hostName | String | The agent hostname. |
| Sysdig.Agent.machineId | String | The host MAC address. |
| Sysdig.Agent.hostId | String | The opaque host identifier. |
| Sysdig.Agent.clusterName | String | The Kubernetes cluster name. |
sysdig-customer-info-get
Gets the Sysdig customer ID and name. The result is cached per integration instance.
Base Command
sysdig-customer-info-get
Input
| Argument Name | Description | Required |
|---|---|---|
| force_refresh | Whether to force a refresh of the cached customer info from the Sysdig API. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Sysdig.Customer.customerId | String | The Sysdig customer ID. |
| Sysdig.Customer.customerName | String | The Sysdig customer name. |
Configuration parameters
url— Your server URL (required)credentials— (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsclassifier— Classifiermapper— Mapper (incoming)isFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalmax_fetch— The maximum number of incidents per fetchfirst_fetch— First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)
Commands (6)
-
create-system-captureTriggers a system capture, recording all system calls at the host level.
-
execute-response-actionExecutes response actions through the Sysdig API. Each action type requires a specific set of parameters. Use the actionType description to see which parameters are required for each action.
-
get-action-executionGets the status and information of a triggered action execution.
-
get-capture-fileGets a system capture based on the capture ID.
-
sysdig-agent-info-getResolves a host MAC address to its Sysdig agent details (agent ID, customer ID, hostname). The result is cached per MAC address.
-
sysdig-customer-info-getGets the Sysdig customer ID and name. The result is cached per integration instance.
""" Sysdig Response Actions Integration """ import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import import re import time from typing import Any import urllib3 # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ SYSTEM_CAPTURES_REQUIRED_FIELDS = ["container_id", "host_name", "capture_name", "agent_id", "customer_id", "machine_id"] RESPONSE_ACTIONS_REQUIRED_FIELDS = ["actionType", "callerId"] RESPONSE_ACTIONS_PARAMS: dict[str, list[str]] = { "KILL_PROCESS": ["host.id", "process.id", "startTime"], "KILL_CONTAINER": ["host.id", "container.id"], "PAUSE_CONTAINER": ["host.id", "container.id"], "STOP_CONTAINER": ["host.id", "container.id"], "UNPAUSE_CONTAINER": ["host.id", "container.id"], "START_CONTAINER": ["host.id", "container.id"], "FILE_QUARANTINE": ["host.id", "path.absolute"], "FILE_ACQUIRE": ["host.id", "path.absolute"], "FILE_UNQUARANTINE": ["host.id", "path.absolute", "quarantined_file_path"], "DELETE_POD": ["kubernetes.cluster.name", "kubernetes.namespace.name", "kubernetes.pod.name"], "ROLLOUT_RESTART": [ "kubernetes.cluster.name", "kubernetes.namespace.name", "kubernetes.workload.type", "kubernetes.workload.name", ], "ISOLATE_NETWORK": [ "kubernetes.cluster.name", "kubernetes.namespace.name", "kubernetes.workload.type", "kubernetes.workload.name", ], "DELETE_NETWORK_POLICY": ["kubernetes.cluster.name", "kubernetes.namespace.name", "network_policy_name"], "GET_LOGS": ["kubernetes.cluster.name", "kubernetes.namespace.name"], "KUBERNETES_VOLUME_SNAPSHOT": ["kubernetes.cluster.name", "kubernetes.namespace.name"], "KUBERNETES_DELETE_VOLUME_SNAPSHOT": [ "kubernetes.cluster.name", "kubernetes.namespace.name", "kubernetes.persistentvolume.claim.name", "kubernetes.volume.snapshot.name", ], "CAPTURE": ["host.id", "capture.remote_storage_configuration_id", "capture.duration_ns", "capture.past_duration_ns"], "IAM_QUARANTINE": ["cloudProvider.name", "cloudProvider.account.id"], "IAM_UNQUARANTINE": ["cloudProvider.name", "cloudProvider.account.id", "iam_policy_name", "ct.user.identitytype", "ct.user"], "MAKE_PRIVATE_CLOUD_RESOURCE": ["cloudProvider.name", "cloudProvider.account.id", "cloudResourceType", "cloudResourceName"], "UNDO_MAKE_PRIVATE_CLOUD_RESOURCE": [ "cloudProvider.name", "cloudProvider.account.id", "cloudResourceType", "cloudResourceName", "previousPublicAccessSettings", ], "CLOUD_VOLUME_SNAPSHOT": ["cloudProvider.name", "cloudProvider.account.id", "cloudProvider.region", "aws.instanceId"], "UNDO_CLOUD_VOLUME_SNAPSHOT": [ "cloudProvider.name", "cloudProvider.account.id", "cloudProvider.region", "snapshotIds", "aws.instanceId", ], "FETCH_CLOUD_LOGS": [ "cloudProvider.name", "cloudProvider.account.id", "cloudProvider.region", "fromTimestamp", "toTimestamp", ], } DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # ISO8601 format with UTC, default in XSOAR """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the service API This Client implements API calls, and does not contain any XSOAR logic. Should only do requests and return data. It inherits from BaseClient defined in CommonServerPython. Most calls use _http_request() that handles proxy, SSL verification, etc. For this implementation, no special attributes defined """ def __init__( self, base_url: str, verify: bool, headers: dict[str, str], proxy: bool, ok_codes: tuple[int, ...] = (200, 201, 202) ): self.base_url = base_url self.verify = verify self.headers = headers self.proxy = proxy self.ok_codes = ok_codes super().__init__(base_url=base_url, verify=verify, headers=headers, proxy=proxy, ok_codes=ok_codes) def call_sysdig_api( self, method: str = "GET", url_suffix: str = "/secure/response-actions/v1alpha1/action-executions", params: dict = None, data: dict = None, json_data: dict = None, resp_type: str = "json", full_url: str = None, ) -> dict[str, Any] | bytes | str: """ Call the Sysdig API Args: method: The HTTP method to use url_suffix: The URL suffix to use params: The parameters to use data: The data to use json_data: The JSON data to use resp_type: The response type to use full_url: The full URL to use Returns: The response from the API """ demisto.debug(f"Calling endpoint: {self.base_url + url_suffix}") return self._http_request( method=method, url_suffix=url_suffix, params=params, data=data, json_data=json_data, resp_type=resp_type, full_url=full_url, ) """ HELPER FUNCTIONS """ def _build_data_payload(args: dict[str, Any]) -> dict[str, Any]: """ Parse the input parameters to the data payload to execute an action Args: args: The input parameters Returns: The data payload """ data = {field: args.get(field) for field in RESPONSE_ACTIONS_REQUIRED_FIELDS} if not all(data.values()): missing_fields = [field for field in RESPONSE_ACTIONS_REQUIRED_FIELDS if not data.get(field)] raise ValueError(f"The following fields are required and cannot be null: {', '.join(missing_fields)}") PARAM_ARG_MAP = { "container.id": "container_id", "host.id": "host_id", "path.absolute": "path_absolute", "process.id": "process_id", "startTime": "startTime", "quarantined_file_path": "quarantined_file_path", "kubernetes.cluster.name": "k8s_cluster_name", "kubernetes.namespace.name": "k8s_namespace_name", "kubernetes.pod.name": "k8s_pod_name", "kubernetes.workload.type": "k8s_workload_type", "kubernetes.workload.name": "k8s_workload_name", "kubernetes.persistentvolume.claim.name": "k8s_pvc_name", "kubernetes.volume.snapshot.name": "k8s_volume_snapshot_name", "kubernetes.container.name": "k8s_container_name", "network_policy_name": "network_policy_name", "network.protocol": "network_protocol", "network.port": "network_port", "network.cidr": "network_cidr", "network.direction": "network_direction", "previous": "previous", "allContainers": "all_containers", "capture.remote_storage_configuration_id": "capture_storage_config_id", "capture.duration_ns": "capture_duration_ns", "capture.past_duration_ns": "capture_past_duration_ns", "capture.filters": "capture_filters", "capture.max_size": "capture_max_size", "capture.token": "capture_token", "cloudProvider.name": "cloud_provider", "cloudProvider.account.id": "cloud_account_id", "cloudProvider.region": "cloud_region", "ct.user.arn": "ct_user_arn", "ct.user.identitytype": "ct_user_identity_type", "ct.user": "ct_user", "ct.originaluser": "ct_original_user", "ct.name": "ct_name", "ct.src": "ct_src", "iam_policy_name": "iam_policy_name", "cloudResourceType": "cloud_resource_type", "cloudResourceName": "cloud_resource_name", "previousPublicAccessSettings": "previous_public_access_settings", "aws.instanceId": "aws_instance_id", "snapshotIds": "snapshot_ids", "fromTimestamp": "from_timestamp", "toTimestamp": "to_timestamp", } INTEGER_PARAMS = {"process.id", "startTime", "capture.duration_ns", "capture.past_duration_ns", "capture.max_size"} parameters: dict[str, Any] = {} for api_key, arg_name in PARAM_ARG_MAP.items(): val = args.get(arg_name) if val in (None, "", "null"): continue if api_key in INTEGER_PARAMS: val = int(val) # type: ignore[arg-type] if api_key == "previous" or api_key == "allContainers": # guardrails-disable-line val = argToBoolean(val) parameters[api_key] = val if args.get("process_id") and "startTime" not in parameters: parameters["startTime"] = -1 data["parameters"] = parameters _validate_response_actions_params(data) return data def _build_capture_payload(args: dict[str, Any]) -> dict[str, Any]: """ Parse the input parameters to the data payload to create a system capture. """ _validate_captures_params(args) # Extract required and optional fields with defaults data = { "containerId": args.get("container_id"), "duration": args.get("scan_duration", 15), # Default duration is 15 seconds "hostName": args.get("host_name"), "name": args.get("capture_name"), "filters": args.get("scap_filter", ""), "bucketName": "", "agent": { "id": args.get("agent_id"), "customer": args.get("customer_id"), "machineID": args.get("machine_id"), "hostName": args.get("host_name"), }, "annotations": {"manual": "true"}, "source": "SDS", "storageType": "S3", "folder": "/", } return data def _validate_captures_params(args: dict[str, Any]) -> None: """ Validate the input parameters to create a system capture. Raise ValueError if any required parameter is missing """ missing_fields = [field for field in SYSTEM_CAPTURES_REQUIRED_FIELDS if not args.get(field) or args.get(field) == "null"] if missing_fields: raise ValueError(f"The following fields are required and cannot be null: {', '.join(missing_fields)}") def _validate_response_actions_params(args: dict[str, Any]) -> None: """ Validate the input parameters to execute an action. Raise ValueError if any required parameter is missing """ actionType: str = args.get("actionType", "") parameters: dict = args.get("parameters", {}) for key in list(parameters): if parameters[key] in ("null", ""): parameters[key] = None required = RESPONSE_ACTIONS_PARAMS.get(actionType, []) missing_params = [param for param in required if parameters.get(param) in (None, "")] if missing_params: raise ValueError(f"Missing required parameters for {actionType}: {', '.join(missing_params)}") def _get_public_api_url(base_url: str) -> str: """ Get the public API URL from the base URL. Args: base_url: The base URL of the Sysdig API Returns: The public API URL """ # Regex to capture the region pattern (like us2, us3, au1, etc.) # This assumes the region is a subdomain that starts with 2 lowercase letters and ends with a digit pattern = re.search(r"https://(?:(?P<region1>[a-z]{2}\d)\.app|app\.(?P<region2>[a-z]{2}\d))\.sysdig\.com", base_url) if pattern: region = pattern.group(1) # Extract the region return f"https://api.{region}.sysdig.com" else: # Edge case for the secure API URL that is us1 return "https://api.us1.sysdig.com" """ COMMAND FUNCTIONS """ def execute_response_action_command(client: Client, args: dict[str, Any]) -> CommandResults: """ Call the Actions Response API """ full_url = _get_public_api_url(client.base_url) + "/secure/response-actions/v1alpha1/action-executions" data = _build_data_payload(args) result: dict = client.call_sysdig_api(method="POST", full_url=full_url, json_data=data) # type: ignore[assignment] readable_output = ( f"## Response Action: {result.get('actionType')}\n" f"Triggered successfully by callerId: **{result.get('callerId')}** with status: **{result.get('status')}**\n" f"Result ID: **{result.get('id')}**\n" f"Parameters: `{result.get('parameters')}`\n" f"Outputs: `{result.get('outputs')}`\n" ) return CommandResults( outputs_prefix="execute_response_action.Output", outputs=result, readable_output=readable_output, raw_response=result, ) def get_action_execution_command(client: Client, args: dict[str, Any]) -> CommandResults: """ Get the status of an action execution """ action_execution_id = args.get("action_execution_id") full_url = _get_public_api_url(client.base_url) + f"/secure/response-actions/v1alpha1/action-executions/{action_execution_id}" result: dict = client.call_sysdig_api(method="GET", full_url=full_url) # type: ignore[assignment] readable_output = ( f"## Action Execution Status\n" f"- **Action Type:** {result.get('actionType')}\n" f"- **Caller ID:** {result.get('callerId')}\n" f"- **Status:** {result.get('status')}\n" f"- **Result ID:** {result.get('id')}\n" f"- **Parameters:** `{result.get('parameters')}`\n" f"- **Outputs:** `{result.get('outputs')}`\n" ) return CommandResults( outputs_prefix="get_action_execution.Output", outputs=result, readable_output=readable_output, raw_response=result, ) def create_system_capture_command(client: Client, args: dict[str, Any]) -> CommandResults: """ Trigger a sysdig system capture """ data = _build_capture_payload(args) result: dict = client.call_sysdig_api(method="POST", url_suffix="/api/v1/captures", json_data=data) # type: ignore[assignment] readable_output = ( f"## Capture: {result.get('capture', {}).get('name')}\n" f"- **Status:** {result.get('capture', {}).get('status')}\n" f"- **Capture ID:** {result.get('capture', {}).get('id')}\n" ) return CommandResults( outputs_prefix="create_system_capture.Output", outputs=result, readable_output=readable_output, raw_response=result, ) def get_capture_file_command(client: Client, args: dict[str, Any]) -> CommandResults: """ Download a sysdig capture file (.scap). You must provide the capture ID. You can then use the scap file to analyze it with Stratoshark for example. """ capture_id = args.get("capture_id") if not capture_id: raise ValueError("capture_id is required") url_suffix = f"/api/v1/captures/{capture_id}/download" # The response is a binary file, so we set the resp_type to 'content' result: str | bytes = client.call_sysdig_api(url_suffix=url_suffix, resp_type="content") # type: ignore[assignment] incident_id = demisto.incident().get("id") file_name = f"{incident_id}_{capture_id}.scap" # Save the file in the War Room demisto.results(fileResult(file_name, result, EntryType.FILE)) readable_output = f"# Capture Taken\n**{file_name}** saved successfully to the War Room" return CommandResults( readable_output=readable_output, outputs_prefix="get_capture_file.Output", ) def _cache_is_valid(entry: dict | None, ttl: int = 3600) -> bool: if not entry or "cached_at" not in entry: return False return (time.time() - entry["cached_at"]) < ttl def get_agent_by_mac_command(client: Client, args: dict[str, Any]) -> CommandResults: machine_id = args.get("machine_id") if not machine_id: raise ValueError("machine_id (MAC address) is required.") ctx = demisto.getIntegrationContext() cache_key = f"agent_{machine_id}" cached = ctx.get(cache_key) force = argToBoolean(args.get("force_refresh", "false")) if _cache_is_valid(cached) and not force: agent = cached["data"] else: result: dict = client.call_sysdig_api("GET", url_suffix="/api/agents/connected") # type: ignore[assignment] agents = result.get("agents", result) if isinstance(result, dict) else result agent = None for a in agents: if a.get("machineId") == machine_id: agent = { "agentId": str(a.get("id", "")), "customerId": str(a.get("customer", "")), "hostName": a.get("hostName", ""), "machineId": a.get("machineId", ""), "hostId": a.get("opaqueUid", ""), "clusterName": (a.get("attributes") or {}).get("clusterName", ""), } ctx[cache_key] = {"data": agent, "cached_at": time.time()} demisto.setIntegrationContext(ctx) break if not agent: raise ValueError(f"No connected agent found with machineId (MAC) '{machine_id}'.") return CommandResults( outputs_prefix="Sysdig.Agent", outputs_key_field="machineId", outputs=agent, readable_output=( f"**Agent ID:** {agent['agentId']}\n" f"**Customer ID:** {agent['customerId']}\n" f"**Hostname:** {agent['hostName']}\n" f"**Host ID:** {agent['hostId']}\n" f"**Cluster:** {agent['clusterName']}" ), ) def get_customer_info_command(client: Client, args: dict[str, Any]) -> CommandResults: ctx = demisto.getIntegrationContext() cached = ctx.get("customer_info") force = argToBoolean(args.get("force_refresh", "false")) if _cache_is_valid(cached) and not force: customer_id = cached["data"]["customer_id"] customer_name = cached["data"].get("customer_name", "") else: result: dict = client.call_sysdig_api("GET", url_suffix="/api/users/me") # type: ignore[assignment] user: dict = result.get("user") or {} customer = user.get("customer") or {} customer_id = customer.get("id") or user.get("customerId") customer_name = customer.get("name") or user.get("customerName", "") if customer_id: ctx["customer_info"] = { "data": {"customer_id": str(customer_id), "customer_name": customer_name}, "cached_at": time.time(), } demisto.setIntegrationContext(ctx) if not customer_id: raise ValueError("Could not retrieve customer ID from Sysdig API.") output = {"customerId": str(customer_id), "customerName": customer_name} return CommandResults( outputs_prefix="Sysdig.Customer", outputs_key_field="customerId", outputs=output, readable_output=f"**Sysdig Customer ID:** {customer_id}\n**Customer Name:** {customer_name}", ) def test_module(client: Client): """ Returning 'ok' indicates that the integration works like it suppose to. Connection to the service is successful. Args: client: The Sysdig client to use for the API call Returns: 'ok' if test passed, anything else will fail the test """ result: dict = client.call_sysdig_api("GET", url_suffix="/api/users/me") # type: ignore[assignment] user: dict = result.get("user") or {} if user and user.get("id"): return "ok" else: return "Test failed. Could not retrieve user information from Sysdig API. Please check your credentials and API URL." def main(): # pragma: no cover """ main function, parses params and runs command functions """ params = demisto.params() # Get the service API key for the Bearer auth from the credentials service api_key = demisto.params().get("credentials", {}).get("password") # get the service API url base_url = params.get("url") # if your Client class inherits from BaseClient, SSL verification is # handled out of the box by it, just pass ``verify_certificate`` to # the Client constructor verify_certificate = not argToBoolean(params.get("insecure", False)) # if your Client class inherits from BaseClient, system proxy is handled # out of the box by it, just pass ``proxy`` to the Client constructor proxy = argToBoolean(params.get("proxy", False)) command = demisto.command() try: headers = {"accept": "application/json", "Authorization": "Bearer " + api_key, "Content-Type": "application/json"} client = Client(base_url=base_url, verify=verify_certificate, headers=headers, proxy=proxy) args = demisto.args() if command == "execute-response-action": result = execute_response_action_command(client, args) elif command == "create-system-capture": result = create_system_capture_command(client, args) elif command == "get-capture-file": result = get_capture_file_command(client, args) elif command == "get-action-execution": result = get_action_execution_command(client, args) elif command == "sysdig-agent-info-get": result = get_agent_by_mac_command(client, args) elif command == "sysdig-customer-info-get": result = get_customer_info_command(client, args) elif command == "test-module": result = test_module(client) else: raise NotImplementedError(f"Command {command} is not implemented") return_results(result) # Returns either str, CommandResults and a list of CommandResults # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{str(e)}") if __name__ in ("__main__", "__builtin__", "builtins"): # pragma: no cover main()