diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md b/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md index b9850d2f5d8..de534b49beb 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md @@ -2497,3 +2497,121 @@ Retrieves a UAM alert enriched with raw SDL event indicators using the v2.1 unif | SentinelOne.AlertWithRawIndicators.Process | Unknown | Process information associated with the alert. | | SentinelOne.AlertWithRawIndicators.Observables | Unknown | Observable indicators such as process name or IP associated with the alert. | | SentinelOne.AlertWithRawIndicators.DetectionTime | Unknown | Scope information at the time of detection. | + +### sentinelone-create-firewall-rule + +*** +Creates a Firewall Control rule for a specified scope and OS to allow or block network traffic to matching endpoints (uses the v2.1 firewall-control endpoint). + +#### Base Command + +`sentinelone-create-firewall-rule` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| name | The name of the firewall rule. | Required | +| action | Defines whether the agent blocks or allows traffic matching the rule parameters. Possible values are: Allow, Block. | Required | +| status | Defines whether the rule is Enabled or Disabled. Possible values are: Enabled, Disabled. | Required | +| direction | Defines the direction of the firewall rule. Possible values are: any, inbound, outbound. | Optional | +| os_types | A comma-separated list of OS types for the rule. Possible values are: linux, windows, macos, windows_legacy. | Optional | +| protocol | The network protocol for the rule. | Optional | +| remote_host_type | The type of remote host to match. Use 'addresses' for IP addresses and 'fqdn' for domains/FQDNs. Possible values are: any, cidr, range, addresses, fqdn. | Optional | +| remote_host_values | A comma-separated list of remote host values. | Optional | +| local_host_type | The type of local host to match. Possible values are: any, cidr, range, addresses, fqdn. | Optional | +| local_host_values | A comma-separated list of local host values. | Optional | +| remote_port_type | The type of remote ports to match. Possible values are: any, ports, range. | Optional | +| remote_port_values | A comma-separated list of remote port numbers (integers between 0 and 65535). | Optional | +| local_port_type | The type of local ports to match. Possible values are: any, ports, range. | Optional | +| local_port_values | A comma-separated list of local port numbers (integers between 0 and 65535). | Optional | +| application_type | The type of application for the rule. Possible values are: any, path, sha1, system. | Optional | +| application_values | A comma-separated list of application values. | Optional | +| location_type | The location type for the rule. Possible values are: all, specific, fallback. | Optional | +| location_ids | A comma-separated list of location IDs (applicable when location_type is 'specific'). | Optional | +| description | Free-text description of the firewall rule. | Optional | +| tag_ids | A comma-separated list of tag IDs to link to this firewall rule. | Optional | +| tenant | Indicates a tenant (global) scope request. Set to true for global scope, or false when filtering by account_ids, site_ids, or group_ids. Possible values are: true, false. | Optional | +| account_ids | A comma-separated list of Account IDs to scope the firewall rule to. | Optional | +| site_ids | A comma-separated list of Site IDs to scope the firewall rule to. | Optional | +| group_ids | A comma-separated list of Group IDs to scope the firewall rule to. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| SentinelOne.FirewallRule.Created.ID | string | The created firewall rule ID. | +| SentinelOne.FirewallRule.Created.Name | string | The name of the firewall rule. | +| SentinelOne.FirewallRule.Created.Action | string | The rule action \(Allow or Block\). | +| SentinelOne.FirewallRule.Created.Status | string | The status of the rule \(Enabled or Disabled\). | +| SentinelOne.FirewallRule.Created.Direction | string | The direction of the firewall rule \(any, inbound, outbound\). | +| SentinelOne.FirewallRule.Created.OsType | string | The primary OS type of the rule. | +| SentinelOne.FirewallRule.Created.OsTypes | Unknown | The list of OS types for the rule. | +| SentinelOne.FirewallRule.Created.Protocol | string | The protocol of the firewall rule. | +| SentinelOne.FirewallRule.Created.RemoteHost | Unknown | The first remote host in the rule. | +| SentinelOne.FirewallRule.Created.RemoteHosts | Unknown | The list of remote hosts in the rule. | +| SentinelOne.FirewallRule.Created.LocalHost | Unknown | The local host structure of the rule. | +| SentinelOne.FirewallRule.Created.RemotePort | Unknown | The remote ports structure of the rule. | +| SentinelOne.FirewallRule.Created.LocalPort | Unknown | The local ports structure of the rule. | +| SentinelOne.FirewallRule.Created.Application | Unknown | The application structure of the rule. | +| SentinelOne.FirewallRule.Created.Location | Unknown | The location associated with the rule. | +| SentinelOne.FirewallRule.Created.Scope | string | The scope of the rule \(global, account, group, site\). | +| SentinelOne.FirewallRule.Created.ScopeId | string | The group, site, or account ID depending on the scope. | +| SentinelOne.FirewallRule.Created.Order | number | The position of the rule in the list of rules. | +| SentinelOne.FirewallRule.Created.Editable | boolean | Whether the rule can be modified at this scope level. | +| SentinelOne.FirewallRule.Created.Creator | string | The full name of the user who created the rule. | +| SentinelOne.FirewallRule.Created.CreatorId | string | The ID of the user who created the rule. | +| SentinelOne.FirewallRule.Created.CreatedAt | date | The date and time of rule creation. | +| SentinelOne.FirewallRule.Created.UpdatedAt | date | The date and time of the last update. | +| SentinelOne.FirewallRule.Created.Description | string | The description of the firewall rule. | +| SentinelOne.FirewallRule.Created.RuleCategory | string | The rule category \(firewall or network_quarantine\). | +| SentinelOne.FirewallRule.Created.TagIds | Unknown | The list of tag IDs linked to this rule. | +| SentinelOne.FirewallRule.Created.TagNames | Unknown | The list of tag names linked to this rule. | +| SentinelOne.FirewallRule.Created.Tags | Unknown | The list of tag objects linked to this rule. | +| SentinelOne.FirewallRule.Created.ProductId | string | The product identifier. | + +### sentinelone-delete-firewall-rule + +*** +Deletes Firewall Control rules that match the filter (uses the v2.1 firewall-control endpoint). + +#### Base Command + +`sentinelone-delete-firewall-rule` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| ids | A comma-separated list of firewall rule IDs to filter by, for example: "225494730938493804,225494730938493915". | Optional | +| account_ids | A comma-separated list of Account IDs to filter by, for example: "225494730938493804,225494730938493915". | Optional | +| site_ids | A comma-separated list of Site IDs to filter by, for example: "225494730938493804,225494730938493915". | Optional | +| group_ids | A comma-separated list of Group IDs to filter by, for example: "225494730938493804,225494730938493915". | Optional | +| tenant | Indicates a tenant (global) scope request. Possible values are: true, false. | Optional | +| query | Free-text search on name, tag, application, or protocol. | Optional | +| scopes | A comma-separated list of scopes to filter firewall rules by (global, account, group, site). Possible values are: global, account, group, site. | Optional | +| applications | A comma-separated list of firewall classes/applications to filter by. | Optional | +| name | Filter firewall rules by exact rule name. | Optional | +| protocols | A comma-separated list of protocols to filter firewall rules by. | Optional | +| os_types | A comma-separated list of OS types to filter firewall rules by. Possible values are: linux, windows, macos, windows_legacy. | Optional | +| directions | A comma-separated list of directions to filter firewall rules by. Possible values are: any, inbound, outbound. | Optional | +| actions | A comma-separated list of actions to filter firewall rules by. Possible values are: Allow, Block. | Optional | +| statuses | A comma-separated list of statuses to filter firewall rules by. Possible values are: Enabled, Disabled. | Optional | +| created_at_lt | Filter firewall rules created before this timestamp (e.g., 2018-02-27T04:49:26.257525Z). | Optional | +| created_at_gt | Filter firewall rules created after this timestamp (e.g., 2018-02-27T04:49:26.257525Z). | Optional | +| created_at_lte | Filter firewall rules created before or at this timestamp (e.g., 2018-02-27T04:49:26.257525Z). | Optional | +| created_at_gte | Filter firewall rules created after or at this timestamp (e.g., 2018-02-27T04:49:26.257525Z). | Optional | +| created_at_between | Filter firewall rules created within this range (inclusive), for example: "1514978764288-1514978999999". | Optional | +| location_ids | A comma-separated list of associated location IDs to filter by. | Optional | +| tag_ids | A comma-separated list of associated tag IDs to filter by. | Optional | +| name_contains | Free-text filter by the rule name (supports multiple comma-separated values). | Optional | +| tag_name_contains | Free-text filter by the tag name (supports multiple comma-separated values). | Optional | +| application_contains | Free-text filter by application (supports multiple comma-separated values). | Optional | +| protocol_contains | Free-text filter by protocol (supports multiple comma-separated values). | Optional | +| service_contains | Free-text filter by service (supports multiple comma-separated values). | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| SentinelOne.FirewallRule.Deleted.Affected | number | The number of firewall rules successfully deleted. | diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py index f11d1fe7ce7..50b25e47fa6 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py @@ -783,6 +783,19 @@ def create_unified_exclusion_request( return response.get("data")[0] return {} + def create_firewall_rule_request(self, filter_data: dict, rule_data: dict) -> dict: + payload = { + "filter": filter_data, + "data": rule_data, + } + response = self._http_request(method="POST", url_suffix="firewall-control", json_data=payload) + return response.get("data") or {} + + def delete_firewall_rule_request(self, filter_data: dict) -> dict: + payload = {"filter": filter_data} + response = self._http_request(method="DELETE", url_suffix="firewall-control", json_data=payload, ok_codes=[200]) + return response.get("data") or {} + def delete_unified_exclusion_request(self, exclusions: list[dict]) -> dict: body = {"data": {"exclusions": exclusions}} response = self._http_request(method="DELETE", url_suffix="unified-exclusions", json_data=body, ok_codes=[200]) @@ -3896,6 +3909,252 @@ def delete_unified_exclusion_command(client: Client, args: dict) -> CommandResul ) +def build_firewall_rule_scope_filter(args: dict) -> dict: + """Validate and build the scope filter for firewall rule commands.""" + tenant = argToBoolean(args["tenant"]) if args.get("tenant") is not None else None + account_ids = argToList(args.get("account_ids")) or None + site_ids = argToList(args.get("site_ids")) or None + group_ids = argToList(args.get("group_ids")) or None + + has_scope_ids = any([account_ids, site_ids, group_ids]) + if not tenant and not has_scope_ids: + raise DemistoException( + "You must provide a scope filter: set 'tenant' to true, or specify 'account_ids', 'site_ids', or 'group_ids'." + ) + if tenant and has_scope_ids: + raise DemistoException("Cannot set 'tenant' to true when 'account_ids', 'site_ids', or 'group_ids' are specified.") + + return assign_params( + tenant=tenant, + accountIds=account_ids, + siteIds=site_ids, + groupIds=group_ids, + ) + + +def build_remote_hosts(args: dict) -> list[dict] | None: + """Validate and build the remoteHosts list for firewall rule creation.""" + remote_host_type = args.get("remote_host_type") + remote_host_values = argToList(args.get("remote_host_values")) + if not remote_host_type: + if remote_host_values: + raise DemistoException("remote_host_type is required when remote_host_values is specified.") + return None + + if remote_host_type == "any": + if remote_host_values: + raise DemistoException("remote_host_values cannot be specified when remote_host_type is 'any'.") + return None + if not remote_host_values: + raise DemistoException("remote_host_values is required when remote_host_type is not 'any'.") + if remote_host_type == "range" and len(remote_host_values) != 2: + raise DemistoException( + "remote_host_values must contain exactly 2 values (start and end IP) when remote_host_type is 'range'." + ) + if remote_host_type in ("fqdn", "cidr"): + return [{"type": remote_host_type, "values": [val]} for val in remote_host_values] + return [{"type": remote_host_type, "values": remote_host_values}] + + +def build_local_host(args: dict) -> dict | None: + """Validate and build the localHost object for firewall rule creation.""" + local_host_type = args.get("local_host_type") + local_host_values = argToList(args.get("local_host_values")) + if not local_host_type: + if local_host_values: + raise DemistoException("local_host_type is required when local_host_values is specified.") + return None + + if local_host_type == "any": + if local_host_values: + raise DemistoException("local_host_values cannot be specified when local_host_type is 'any'.") + return None + if not local_host_values: + raise DemistoException("local_host_values is required when local_host_type is not 'any'.") + if local_host_type == "range" and len(local_host_values) != 2: + raise DemistoException( + "local_host_values must contain exactly 2 values (start and end IP) when local_host_type is 'range'." + ) + if local_host_type in ("fqdn", "cidr") and len(local_host_values) != 1: + raise DemistoException(f"local_host_values must contain exactly 1 value when local_host_type is '{local_host_type}'.") + return {"type": local_host_type, "values": local_host_values} + + +def build_firewall_rule_port(args: dict, prefix: str) -> dict | None: + """Validate and build a port object (remotePort or localPort) for firewall rule creation.""" + type_arg = f"{prefix}_type" + values_arg = f"{prefix}_values" + port_type = args.get(type_arg) + port_values_raw = argToList(args.get(values_arg)) + if not port_type: + if port_values_raw: + raise DemistoException(f"{type_arg} is required when {values_arg} is specified.") + return None + + if port_type == "any": + if port_values_raw: + raise DemistoException(f"{values_arg} cannot be specified when {type_arg} is 'any'.") + return None + if not port_values_raw: + raise DemistoException(f"{values_arg} is required when {type_arg} is not 'any'.") + if port_type == "range" and len(port_values_raw) != 2: + raise DemistoException( + f"{values_arg} must contain exactly 2 port numbers (start and end port) when {type_arg} is 'range'." + ) + port_values = [arg_to_number(p, arg_name=values_arg) for p in port_values_raw] + return {"type": port_type, "values": port_values} + + +def build_firewall_rule_application(args: dict) -> dict | None: + """Validate and build the application object for firewall rule creation.""" + application_type = args.get("application_type") + application_values = argToList(args.get("application_values")) + if not application_type: + if application_values: + raise DemistoException("application_type is required when application_values is specified.") + return None + + if application_type == "any": + if application_values: + raise DemistoException("application_values cannot be specified when application_type is 'any'.") + return None + if not application_values: + raise DemistoException("application_values is required when application_type is not 'any'.") + return {"type": application_type, "values": application_values} + + +def build_firewall_rule_location(args: dict) -> dict | None: + """Validate and build the location object for firewall rule creation.""" + location_type = args.get("location_type") + location_ids = argToList(args.get("location_ids")) + if not location_type: + if location_ids: + raise DemistoException("location_type is required when location_ids is specified.") + return None + + if location_type != "specific" and location_ids: + raise DemistoException("location_ids can only be specified when location_type is 'specific'.") + if location_type == "specific" and not location_ids: + raise DemistoException("location_ids is required when location_type is 'specific'.") + location_values = [{"id": loc_id} for loc_id in location_ids] if location_ids else None + return assign_params(type=location_type, values=location_values) + + +def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: + """ + Create a Firewall Control rule for a specified scope (v2.1 firewall-control endpoint). + """ + filter_data = build_firewall_rule_scope_filter(args) + + rule_data = assign_params( + name=args["name"], + action=args["action"], + status=args["status"], + direction=args.get("direction"), + osTypes=argToList(args.get("os_types")) or None, + protocol=args.get("protocol"), + description=args.get("description"), + tagIds=argToList(args.get("tag_ids")) or None, + remoteHosts=build_remote_hosts(args), + localHost=build_local_host(args), + remotePort=build_firewall_rule_port(args, "remote_port"), + localPort=build_firewall_rule_port(args, "local_port"), + application=build_firewall_rule_application(args), + location=build_firewall_rule_location(args), + ) + + new_rule = client.create_firewall_rule_request(filter_data=filter_data, rule_data=rule_data) + + context_entry: dict = {} + if new_rule: + context_entry = { + "ID": new_rule.get("id"), + "Name": new_rule.get("name"), + "Action": new_rule.get("action"), + "Status": new_rule.get("status"), + "Direction": new_rule.get("direction"), + "OsType": new_rule.get("osType"), + "OsTypes": new_rule.get("osTypes"), + "Protocol": new_rule.get("protocol"), + "RemoteHost": new_rule.get("remoteHost"), + "RemoteHosts": new_rule.get("remoteHosts"), + "LocalHost": new_rule.get("localHost"), + "RemotePort": new_rule.get("remotePort"), + "LocalPort": new_rule.get("localPort"), + "Application": new_rule.get("application"), + "Location": new_rule.get("location"), + "Scope": new_rule.get("scope"), + "ScopeId": new_rule.get("scopeId"), + "Order": new_rule.get("order"), + "Editable": new_rule.get("editable"), + "Creator": new_rule.get("creator"), + "CreatorId": new_rule.get("creatorId"), + "CreatedAt": new_rule.get("createdAt"), + "UpdatedAt": new_rule.get("updatedAt"), + "Description": new_rule.get("description"), + "RuleCategory": new_rule.get("ruleCategory"), + "TagIds": new_rule.get("tagIds"), + "TagNames": new_rule.get("tagNames"), + "Tags": new_rule.get("tags"), + "ProductId": new_rule.get("productId"), + } + + return CommandResults( + readable_output=tableToMarkdown( + "Sentinel One - Create Firewall Rule.", context_entry, removeNull=True, headerTransform=pascalToSpace + ), + outputs_prefix="SentinelOne.FirewallRule.Created", + outputs_key_field="ID", + outputs=context_entry, + raw_response=new_rule, + ) + + +def delete_firewall_rule_command(client: Client, args: dict) -> CommandResults: + """ + Delete Firewall Control rules that match the filter (v2.1 firewall-control endpoint). + """ + scope_filter = build_firewall_rule_scope_filter(args) + rule_filter = assign_params( + ids=argToList(args.get("ids")) or None, + query=args.get("query"), + scopes=argToList(args.get("scopes")) or None, + applications=argToList(args.get("applications")) or None, + name=args.get("name"), + protocols=argToList(args.get("protocols")) or None, + osTypes=argToList(args.get("os_types")) or None, + directions=argToList(args.get("directions")) or None, + actions=argToList(args.get("actions")) or None, + statuses=argToList(args.get("statuses")) or None, + createdAt__lt=args.get("created_at_lt"), + createdAt__gt=args.get("created_at_gt"), + createdAt__lte=args.get("created_at_lte"), + createdAt__gte=args.get("created_at_gte"), + createdAt__between=args.get("created_at_between"), + locationIds=argToList(args.get("location_ids")) or None, + tagIds=argToList(args.get("tag_ids")) or None, + name__contains=argToList(args.get("name_contains")) or None, + tagName__contains=argToList(args.get("tag_name_contains")) or None, + application__contains=argToList(args.get("application_contains")) or None, + protocol__contains=argToList(args.get("protocol_contains")) or None, + service__contains=argToList(args.get("service_contains")) or None, + ) + + if not rule_filter: + raise DemistoException("You must provide at least one rule filter parameter to delete firewall rules.") + + filter_data = {**scope_filter, **rule_filter} + response = client.delete_firewall_rule_request(filter_data=filter_data) + affected = response.get("affected", 0) + + return CommandResults( + readable_output="Sentinel One - Firewall Rules Deleted.", + outputs_prefix="SentinelOne.FirewallRule.Deleted", + outputs={"Affected": affected}, + raw_response=response, + ) + + def get_item_ids_from_whitelist(client: Client, item: str, exclusion_type: str, os_type: str = None) -> list[str | None]: """ Return the IDs of the hash from the white. Helper function for remove_item_from_whitelist @@ -6613,6 +6872,8 @@ def main(): "sentinelone-get-unified-exclusions": get_unified_exclusions_command, "sentinelone-create-unified-exclusion": create_unified_exclusion_command, "sentinelone-delete-unified-exclusions": delete_unified_exclusion_command, + "sentinelone-create-firewall-rule": create_firewall_rule_command, + "sentinelone-delete-firewall-rule": delete_firewall_rule_command, "sentinelone-get-alert-with-raw-indicators": get_alert_with_raw_indicators_command, }, "commands_with_params": { diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml index 5f81708be0f..65a5b8dedb2 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml @@ -821,6 +821,324 @@ script: - contextPath: SentinelOne.Threat.MarkedAsBenign description: Whether the threat is marked as benign. Relevant for version 2.0 only. type: Boolean + - arguments: + - description: The name of the firewall rule. + name: name + required: true + - auto: PREDEFINED + description: Defines whether the agent blocks or allows traffic matching the rule parameters. + name: action + predefined: + - Allow + - Block + required: true + - auto: PREDEFINED + description: Defines whether the rule is Enabled or Disabled. + name: status + predefined: + - Enabled + - Disabled + required: true + - auto: PREDEFINED + description: Defines the direction of the firewall rule. + name: direction + predefined: + - any + - inbound + - outbound + - auto: PREDEFINED + description: A comma-separated list of OS types for the rule. + isArray: true + name: os_types + predefined: + - linux + - windows + - macos + - windows_legacy + - description: The network protocol for the rule. + name: protocol + - auto: PREDEFINED + description: The type of remote host to match. Use 'addresses' for IP addresses and 'fqdn' for domains/FQDNs. + name: remote_host_type + predefined: + - any + - cidr + - range + - addresses + - fqdn + - description: A comma-separated list of remote host values. + isArray: true + name: remote_host_values + - auto: PREDEFINED + description: The type of local host to match. + name: local_host_type + predefined: + - any + - cidr + - range + - addresses + - fqdn + - description: A comma-separated list of local host values. + isArray: true + name: local_host_values + - auto: PREDEFINED + description: The type of remote ports to match. + name: remote_port_type + predefined: + - any + - ports + - range + - description: A comma-separated list of remote port numbers (integers between 0 and 65535). + isArray: true + name: remote_port_values + - auto: PREDEFINED + description: The type of local ports to match. + name: local_port_type + predefined: + - any + - ports + - range + - description: A comma-separated list of local port numbers (integers between 0 and 65535). + isArray: true + name: local_port_values + - auto: PREDEFINED + description: The type of application for the rule. + name: application_type + predefined: + - any + - path + - sha1 + - system + - description: A comma-separated list of application values. + isArray: true + name: application_values + - auto: PREDEFINED + description: The location type for the rule. + name: location_type + predefined: + - all + - specific + - fallback + - description: A comma-separated list of location IDs (applicable when location_type is 'specific'). + isArray: true + name: location_ids + - description: Free-text description of the firewall rule. + name: description + - description: A comma-separated list of tag IDs to link to this firewall rule. + isArray: true + name: tag_ids + - auto: PREDEFINED + description: Indicates a tenant (global) scope request. Set to true for global scope, or false when filtering by account_ids, site_ids, or group_ids. + name: tenant + predefined: + - 'true' + - 'false' + - description: A comma-separated list of Account IDs to scope the firewall rule to. + isArray: true + name: account_ids + - description: A comma-separated list of Site IDs to scope the firewall rule to. + isArray: true + name: site_ids + - description: A comma-separated list of Group IDs to scope the firewall rule to. + isArray: true + name: group_ids + description: Creates a Firewall Control rule for a specified scope and OS to allow or block network traffic to matching endpoints (uses the v2.1 firewall-control endpoint). + name: sentinelone-create-firewall-rule + outputs: + - contextPath: SentinelOne.FirewallRule.Created.ID + description: The created firewall rule ID. + type: string + - contextPath: SentinelOne.FirewallRule.Created.Name + description: The name of the firewall rule. + type: string + - contextPath: SentinelOne.FirewallRule.Created.Action + description: The rule action (Allow or Block). + type: string + - contextPath: SentinelOne.FirewallRule.Created.Status + description: The status of the rule (Enabled or Disabled). + type: string + - contextPath: SentinelOne.FirewallRule.Created.Direction + description: The direction of the firewall rule (any, inbound, outbound). + type: string + - contextPath: SentinelOne.FirewallRule.Created.OsType + description: The primary OS type of the rule. + type: string + - contextPath: SentinelOne.FirewallRule.Created.OsTypes + description: The list of OS types for the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.Protocol + description: The protocol of the firewall rule. + type: string + - contextPath: SentinelOne.FirewallRule.Created.RemoteHost + description: The first remote host in the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.RemoteHosts + description: The list of remote hosts in the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.LocalHost + description: The local host structure of the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.RemotePort + description: The remote ports structure of the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.LocalPort + description: The local ports structure of the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.Application + description: The application structure of the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.Location + description: The location associated with the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.Scope + description: The scope of the rule (global, account, group, site). + type: string + - contextPath: SentinelOne.FirewallRule.Created.ScopeId + description: The group, site, or account ID depending on the scope. + type: string + - contextPath: SentinelOne.FirewallRule.Created.Order + description: The position of the rule in the list of rules. + type: number + - contextPath: SentinelOne.FirewallRule.Created.Editable + description: Whether the rule can be modified at this scope level. + type: boolean + - contextPath: SentinelOne.FirewallRule.Created.Creator + description: The full name of the user who created the rule. + type: string + - contextPath: SentinelOne.FirewallRule.Created.CreatorId + description: The ID of the user who created the rule. + type: string + - contextPath: SentinelOne.FirewallRule.Created.CreatedAt + description: The date and time of rule creation. + type: date + - contextPath: SentinelOne.FirewallRule.Created.UpdatedAt + description: The date and time of the last update. + type: date + - contextPath: SentinelOne.FirewallRule.Created.Description + description: The description of the firewall rule. + type: string + - contextPath: SentinelOne.FirewallRule.Created.RuleCategory + description: The rule category (firewall or network_quarantine). + type: string + - contextPath: SentinelOne.FirewallRule.Created.TagIds + description: The list of tag IDs linked to this rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.TagNames + description: The list of tag names linked to this rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.Tags + description: The list of tag objects linked to this rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Created.ProductId + description: The product identifier. + type: string + - arguments: + - description: 'A comma-separated list of firewall rule IDs to filter by, for example: "225494730938493804,225494730938493915".' + isArray: true + name: ids + - description: 'A comma-separated list of Account IDs to filter by, for example: "225494730938493804,225494730938493915".' + isArray: true + name: account_ids + - description: 'A comma-separated list of Site IDs to filter by, for example: "225494730938493804,225494730938493915".' + isArray: true + name: site_ids + - description: 'A comma-separated list of Group IDs to filter by, for example: "225494730938493804,225494730938493915".' + isArray: true + name: group_ids + - auto: PREDEFINED + description: Indicates a tenant (global) scope request. + name: tenant + predefined: + - 'true' + - 'false' + - description: Free-text search on name, tag, application, or protocol. + name: query + - auto: PREDEFINED + description: A comma-separated list of scopes to filter firewall rules by (global, account, group, site). + isArray: true + name: scopes + predefined: + - global + - account + - group + - site + - description: A comma-separated list of firewall classes/applications to filter by. + isArray: true + name: applications + - description: Filter firewall rules by exact rule name. + name: name + - description: A comma-separated list of protocols to filter firewall rules by. + isArray: true + name: protocols + - auto: PREDEFINED + description: A comma-separated list of OS types to filter firewall rules by. + isArray: true + name: os_types + predefined: + - linux + - windows + - macos + - windows_legacy + - auto: PREDEFINED + description: A comma-separated list of directions to filter firewall rules by. + isArray: true + name: directions + predefined: + - any + - inbound + - outbound + - auto: PREDEFINED + description: A comma-separated list of actions to filter firewall rules by. + isArray: true + name: actions + predefined: + - Allow + - Block + - auto: PREDEFINED + description: A comma-separated list of statuses to filter firewall rules by. + isArray: true + name: statuses + predefined: + - Enabled + - Disabled + - description: 'Filter firewall rules created before this timestamp (e.g., 2018-02-27T04:49:26.257525Z).' + name: created_at_lt + - description: 'Filter firewall rules created after this timestamp (e.g., 2018-02-27T04:49:26.257525Z).' + name: created_at_gt + - description: 'Filter firewall rules created before or at this timestamp (e.g., 2018-02-27T04:49:26.257525Z).' + name: created_at_lte + - description: 'Filter firewall rules created after or at this timestamp (e.g., 2018-02-27T04:49:26.257525Z).' + name: created_at_gte + - description: 'Filter firewall rules created within this range (inclusive), for example: "1514978764288-1514978999999".' + name: created_at_between + - description: A comma-separated list of associated location IDs to filter by. + isArray: true + name: location_ids + - description: A comma-separated list of associated tag IDs to filter by. + isArray: true + name: tag_ids + - description: Free-text filter by the rule name (supports multiple comma-separated values). + isArray: true + name: name_contains + - description: Free-text filter by the tag name (supports multiple comma-separated values). + isArray: true + name: tag_name_contains + - description: Free-text filter by application (supports multiple comma-separated values). + isArray: true + name: application_contains + - description: Free-text filter by protocol (supports multiple comma-separated values). + isArray: true + name: protocol_contains + - description: Free-text filter by service (supports multiple comma-separated values). + isArray: true + name: service_contains + description: Deletes Firewall Control rules that match the filter (uses the v2.1 firewall-control endpoint). + execution: true + name: sentinelone-delete-firewall-rule + outputs: + - contextPath: SentinelOne.FirewallRule.Deleted.Affected + description: The number of firewall rules successfully deleted. + type: number - arguments: - description: 'A comma-separated list of group IDs by which to filter, for example: "225494730938493804,225494730938493915".' isArray: true diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py index 90e570343d4..63fe42b62cc 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py @@ -2905,3 +2905,282 @@ def test_get_alert_with_raw_indicators(mocker, requests_mock): assert outputs["Process"]["cmdLine"] == "testd --host=unix:///var/run/test.sock" assert requests_mock.last_request.method == "POST" assert "alertWithRawIndicators" in requests_mock.last_request.json()["query"] + + +def test_create_firewall_rule(mocker, requests_mock): + """ + When: + sentinelone-create-firewall-rule command is called to block remote IPs/domains + Returns: + The created firewall rule item with expected request payload and context output. + """ + raw_response = { + "data": { + "id": "225494730938493804", + "name": "Block Malicious IP and Domain", + "action": "Block", + "status": "Enabled", + "direction": "outbound", + "osType": "windows", + "osTypes": ["windows", "linux"], + "protocol": "any", + "remoteHosts": [ + { + "type": "addresses", + "values": ["test_ip"], + } + ], + "scope": "site", + "scopeId": "225494730938493805", + "order": 1, + "editable": True, + "creator": "admin", + "creatorId": "225494730938493800", + "createdAt": "2026-10-27T12:00:00.000000Z", + "updatedAt": "2026-10-27T12:00:00.000000Z", + "description": "Block suspicious indicator", + "ruleCategory": "firewall", + } + } + requests_mock.post("https://usea1.sentinelone.net/web/api/v2.1/firewall-control", json=raw_response) + + mocker.patch.object( + demisto, + "params", + return_value={"token": "token", "url": "https://usea1.sentinelone.net", "api_version": "2.1"}, + ) + mocker.patch.object(demisto, "command", return_value="sentinelone-create-firewall-rule") + mocker.patch.object( + demisto, + "args", + return_value={ + "name": "Block Malicious IP and Domain", + "action": "Block", + "status": "Enabled", + "direction": "outbound", + "os_types": "windows,linux", + "protocol": "any", + "remote_host_type": "addresses", + "remote_host_values": "test_ip", + "site_ids": "225494730938493805", + "description": "Block suspicious indicator", + }, + ) + + mock_return_results = mocker.patch.object(sentinelone_v2, "return_results") + + main() + + call = mock_return_results.call_args_list + outputs = call[0].args[0].outputs + + assert outputs["ID"] == "225494730938493804" + assert outputs["Name"] == "Block Malicious IP and Domain" + assert outputs["Action"] == "Block" + assert outputs["Status"] == "Enabled" + assert outputs["OsTypes"] == ["windows", "linux"] + assert outputs["RemoteHosts"] == [{"type": "addresses", "values": ["test_ip"]}] + assert requests_mock.last_request.json() == { + "filter": { + "siteIds": ["225494730938493805"], + }, + "data": { + "name": "Block Malicious IP and Domain", + "action": "Block", + "status": "Enabled", + "direction": "outbound", + "osTypes": ["windows", "linux"], + "protocol": "any", + "remoteHosts": [{"type": "addresses", "values": ["test_ip"]}], + "description": "Block suspicious indicator", + }, + } + + +def test_create_firewall_rule_full_schema_and_validations(mocker, requests_mock): + """ + When: + sentinelone-create-firewall-rule command is called with full schema fields (fqdn, ports, application, location) + or with missing scope / missing values + Returns: + Properly structured payload for full schema fields and raises DemistoException on invalid input combinations. + """ + raw_response = { + "data": { + "id": "225494730938493999", + "name": "Block Domain and Ports", + "action": "Block", + "status": "Enabled", + } + } + requests_mock.post("https://usea1.sentinelone.net/web/api/v2.1/firewall-control", json=raw_response) + + mocker.patch.object( + demisto, + "params", + return_value={"token": "token", "url": "https://usea1.sentinelone.net", "api_version": "2.1"}, + ) + mocker.patch.object(demisto, "command", return_value="sentinelone-create-firewall-rule") + mocker.patch.object( + demisto, + "args", + return_value={ + "name": "Block Domain and Ports", + "action": "Block", + "status": "Enabled", + "tenant": "true", + "remote_host_type": "fqdn", + "remote_host_values": "malicious.example.com,bad.example.org", + "local_host_type": "cidr", + "local_host_values": "10.0.0.0/8", + "remote_port_type": "ports", + "remote_port_values": "80,443", + "local_port_type": "any", + "application_type": "path", + "application_values": "C:\\bad.exe", + "location_type": "specific", + "location_ids": "225494730938493804", + "tag_ids": "225494730938493915", + }, + ) + + mock_return_results = mocker.patch.object(sentinelone_v2, "return_results") + main() + + assert requests_mock.last_request.json() == { + "filter": { + "tenant": True, + }, + "data": { + "name": "Block Domain and Ports", + "action": "Block", + "status": "Enabled", + "remoteHosts": [ + {"type": "fqdn", "values": ["malicious.example.com"]}, + {"type": "fqdn", "values": ["bad.example.org"]}, + ], + "localHost": {"type": "cidr", "values": ["10.0.0.0/8"]}, + "remotePort": {"type": "ports", "values": [80, 443]}, + "application": {"type": "path", "values": ["C:\\bad.exe"]}, + "location": {"type": "specific", "values": [{"id": "225494730938493804"}]}, + "tagIds": ["225494730938493915"], + }, + } + assert mock_return_results.call_args_list[0].args[0].outputs["ID"] == "225494730938493999" + + client = sentinelone_v2.Client(base_url="https://usea1.sentinelone.net/web/api/v2.1/", verify=False, proxy=False, headers={}) + with pytest.raises(sentinelone_v2.DemistoException, match="You must provide a scope filter"): + sentinelone_v2.create_firewall_rule_command( + client, + {"name": "Rule", "action": "Block", "status": "Enabled"}, + ) + with pytest.raises(sentinelone_v2.DemistoException, match="You must provide a scope filter"): + sentinelone_v2.create_firewall_rule_command( + client, + {"name": "Rule", "action": "Block", "status": "Enabled", "tenant": "false"}, + ) + with pytest.raises(sentinelone_v2.DemistoException, match="Cannot set 'tenant' to true"): + sentinelone_v2.create_firewall_rule_command( + client, + {"name": "Rule", "action": "Block", "status": "Enabled", "tenant": "true", "site_ids": "225494730938493805"}, + ) + with pytest.raises(sentinelone_v2.DemistoException, match="remote_host_values cannot be specified"): + sentinelone_v2.create_firewall_rule_command( + client, + { + "name": "Rule", + "action": "Block", + "status": "Enabled", + "tenant": "true", + "remote_host_type": "any", + "remote_host_values": "192.168.1.1", + }, + ) + with pytest.raises(sentinelone_v2.DemistoException, match="remote_host_values is required"): + sentinelone_v2.create_firewall_rule_command( + client, + {"name": "Rule", "action": "Block", "status": "Enabled", "tenant": "true", "remote_host_type": "fqdn"}, + ) + with pytest.raises(sentinelone_v2.DemistoException, match="remote_host_values must contain exactly 2 values"): + sentinelone_v2.create_firewall_rule_command( + client, + { + "name": "Rule", + "action": "Block", + "status": "Enabled", + "tenant": "true", + "remote_host_type": "range", + "remote_host_values": "192.168.1.1,192.168.1.10,192.168.1.20", + }, + ) + with pytest.raises(sentinelone_v2.DemistoException, match="location_ids can only be specified"): + sentinelone_v2.create_firewall_rule_command( + client, + { + "name": "Rule", + "action": "Block", + "status": "Enabled", + "tenant": "true", + "location_type": "all", + "location_ids": "225494730938493804", + }, + ) + + +def test_delete_firewall_rule(mocker, requests_mock): + """ + When: + sentinelone-delete-firewall-rule command is called with filter parameters + Returns: + Number of firewall rules successfully deleted as reported by the API, and raises DemistoException when no filter is given. + """ + raw_response = {"data": {"affected": 2}} + requests_mock.delete("https://usea1.sentinelone.net/web/api/v2.1/firewall-control", json=raw_response) + + mocker.patch.object( + demisto, + "params", + return_value={"token": "token", "url": "https://usea1.sentinelone.net", "api_version": "2.1"}, + ) + mocker.patch.object(demisto, "command", return_value="sentinelone-delete-firewall-rule") + mocker.patch.object( + demisto, + "args", + return_value={ + "ids": "225494730938493804,225494730938493915", + "site_ids": "225494730938493805", + "actions": "Block", + "statuses": "Enabled", + }, + ) + + mock_return_results = mocker.patch.object(sentinelone_v2, "return_results") + + main() + + call = mock_return_results.call_args_list + outputs = call[0].args[0].outputs + + assert outputs["Affected"] == 2 + assert requests_mock.last_request.method == "DELETE" + assert requests_mock.last_request.json() == { + "filter": { + "ids": ["225494730938493804", "225494730938493915"], + "siteIds": ["225494730938493805"], + "actions": ["Block"], + "statuses": ["Enabled"], + } + } + + client = sentinelone_v2.Client(base_url="https://usea1.sentinelone.net/web/api/v2.1/", verify=False, proxy=False, headers={}) + with pytest.raises(sentinelone_v2.DemistoException, match="You must provide a scope filter"): + sentinelone_v2.delete_firewall_rule_command(client, {}) + with pytest.raises(sentinelone_v2.DemistoException, match="You must provide a scope filter"): + sentinelone_v2.delete_firewall_rule_command(client, {"tenant": "false", "ids": "225494730938493804"}) + with pytest.raises(sentinelone_v2.DemistoException, match="Cannot set 'tenant' to true"): + sentinelone_v2.delete_firewall_rule_command( + client, + {"tenant": "true", "site_ids": "225494730938493805", "ids": "225494730938493804"}, + ) + with pytest.raises(sentinelone_v2.DemistoException, match="You must provide at least one rule filter parameter"): + sentinelone_v2.delete_firewall_rule_command(client, {"tenant": "true"}) diff --git a/Packs/SentinelOne/README.md b/Packs/SentinelOne/README.md index 82a3f5bc9c1..b49cb9fa332 100644 --- a/Packs/SentinelOne/README.md +++ b/Packs/SentinelOne/README.md @@ -10,6 +10,7 @@ This pack enables you to - Get information about threats, mark a suspicious behavior as a threat, and mitigate threats. - Get information about the system sites and reactivate a site if necessary. - Get information about activities, events and processes in the system. +- Create and delete Firewall Control rules to allow or block network traffic (such as IP addresses, CIDR blocks, and domains/FQDNs). The **Sentinel One - Endpoint data collection** playbook collects endpoint information by using SentinelOne commands. The pack includes the **SentinelOne v2** integration and the **Sentinel One - Endpoint data collection** playbook. diff --git a/Packs/SentinelOne/ReleaseNotes/3_3_19.md b/Packs/SentinelOne/ReleaseNotes/3_3_19.md new file mode 100644 index 00000000000..f4d3dc8ec2b --- /dev/null +++ b/Packs/SentinelOne/ReleaseNotes/3_3_19.md @@ -0,0 +1,7 @@ + +#### Integrations + +##### SentinelOne v2 + +- Added the ***sentinelone-create-firewall-rule*** command that creates a Firewall Control rule for a specified scope and OS to allow or block network traffic using the v2.1 firewall-control endpoint. +- Added the ***sentinelone-delete-firewall-rule*** command that deletes Firewall Control rules matching the specified filter using the v2.1 firewall-control endpoint. \ No newline at end of file