From b99a7b56f3b221ab11dbf5d4cfaf79b17efb3308 Mon Sep 17 00:00:00 2001 From: "ioannis.panagopoulos" Date: Tue, 27 Oct 2026 16:29:05 +0200 Subject: [PATCH 1/5] added firewall control rules commands --- .../Integrations/SentinelOne-V2/README.md | 118 +++++++ .../SentinelOne-V2/SentinelOne-V2.py | 221 ++++++++++++ .../SentinelOne-V2/SentinelOne-V2.yml | 318 ++++++++++++++++++ .../SentinelOne-V2/SentinelOne-V2_test.py | 222 ++++++++++++ Packs/SentinelOne/README.md | 1 + Packs/SentinelOne/ReleaseNotes/3_3_17.md | 6 + Packs/SentinelOne/pack_metadata.json | 2 +- 7 files changed, 887 insertions(+), 1 deletion(-) create mode 100644 Packs/SentinelOne/ReleaseNotes/3_3_17.md diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md b/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md index 0f555255ddff..88aa53d82999 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 (e.g., windows, macos, linux, windows_legacy). Possible values are: linux, windows, macos, windows_legacy. | Optional | +| protocol | The network protocol for the rule (e.g., any, tcp, udp, icmp). | Optional | +| remote_host_type | The type of remote host to match (any, cidr, range, addresses, fqdn). 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 (e.g., IP addresses, CIDR blocks, IP ranges, or domains/FQDNs). | Optional | +| local_host_type | The type of local host to match (any, cidr, range, addresses, fqdn). 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 (any, ports, range). 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 (any, ports, range). 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 (any, path, sha1, system). 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 (all, specific, fallback). 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.ID | String | The created firewall rule ID. | +| SentinelOne.FirewallRule.Name | String | The name of the firewall rule. | +| SentinelOne.FirewallRule.Action | String | The rule action (Allow or Block). | +| SentinelOne.FirewallRule.Status | String | The status of the rule (Enabled or Disabled). | +| SentinelOne.FirewallRule.Direction | String | The direction of the firewall rule (any, inbound, outbound). | +| SentinelOne.FirewallRule.OsType | String | The primary OS type of the rule. | +| SentinelOne.FirewallRule.OsTypes | Unknown | The list of OS types for the rule. | +| SentinelOne.FirewallRule.Protocol | String | The protocol of the firewall rule. | +| SentinelOne.FirewallRule.RemoteHost | Unknown | The first remote host in the rule. | +| SentinelOne.FirewallRule.RemoteHosts | Unknown | The list of remote hosts in the rule. | +| SentinelOne.FirewallRule.LocalHost | Unknown | The local host structure of the rule. | +| SentinelOne.FirewallRule.RemotePort | Unknown | The remote ports structure of the rule. | +| SentinelOne.FirewallRule.LocalPort | Unknown | The local ports structure of the rule. | +| SentinelOne.FirewallRule.Application | Unknown | The application structure of the rule. | +| SentinelOne.FirewallRule.Location | Unknown | The location associated with the rule. | +| SentinelOne.FirewallRule.Scope | String | The scope of the rule (global, account, group, site). | +| SentinelOne.FirewallRule.ScopeId | String | The group, site, or account ID depending on the scope. | +| SentinelOne.FirewallRule.Order | Number | The position of the rule in the list of rules. | +| SentinelOne.FirewallRule.Editable | Boolean | Whether the rule can be modified at this scope level. | +| SentinelOne.FirewallRule.Creator | String | The full name of the user who created the rule. | +| SentinelOne.FirewallRule.CreatorId | String | The ID of the user who created the rule. | +| SentinelOne.FirewallRule.CreatedAt | Date | The date and time of rule creation. | +| SentinelOne.FirewallRule.UpdatedAt | Date | The date and time of the last update. | +| SentinelOne.FirewallRule.Description | String | The description of the firewall rule. | +| SentinelOne.FirewallRule.RuleCategory | String | The rule category (firewall or network_quarantine). | +| SentinelOne.FirewallRule.TagIds | Unknown | The list of tag IDs linked to this rule. | +| SentinelOne.FirewallRule.TagNames | Unknown | The list of tag names linked to this rule. | +| SentinelOne.FirewallRule.Tags | Unknown | The list of tag objects linked to this rule. | +| SentinelOne.FirewallRule.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 (linux, windows, macos, windows_legacy). Possible values are: linux, windows, macos, windows_legacy. | Optional | +| directions | A comma-separated list of directions to filter firewall rules by (any, inbound, outbound). Possible values are: any, inbound, outbound. | Optional | +| actions | A comma-separated list of actions to filter firewall rules by (Allow, Block). Possible values are: Allow, Block. | Optional | +| statuses | A comma-separated list of statuses to filter firewall rules by (Enabled, Disabled). 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 dba897dd1962..aff59cb39a63 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,212 @@ def delete_unified_exclusion_command(client: Client, args: dict) -> CommandResul ) +def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: + """ + Create a Firewall Control rule for a specified scope (v2.1 firewall-control endpoint). + """ + name = args["name"] + action = args["action"] + status = args["status"] + direction = args.get("direction") + os_types = argToList(args.get("os_types")) or None + protocol = args.get("protocol") + description = args.get("description") + tag_ids = argToList(args.get("tag_ids")) or None + + 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 + + if tenant is None and not any([account_ids, site_ids, group_ids]): + raise DemistoException( + "You must provide a scope filter: set 'tenant' to true, or specify 'account_ids', 'site_ids', or 'group_ids'." + ) + + remote_host_type = args.get("remote_host_type") + remote_host_values = argToList(args.get("remote_host_values")) + remote_hosts = None + if remote_host_type: + if remote_host_type != "any" and not remote_host_values: + raise DemistoException("remote_host_values is required when remote_host_type is not 'any'.") + remote_hosts = [assign_params(type=remote_host_type, values=remote_host_values or None)] + elif remote_host_values: + raise DemistoException("remote_host_type is required when remote_host_values is specified.") + + local_host_type = args.get("local_host_type") + local_host_values = argToList(args.get("local_host_values")) + local_host = None + if local_host_type: + if local_host_type != "any" and not local_host_values: + raise DemistoException("local_host_values is required when local_host_type is not 'any'.") + local_host = assign_params(type=local_host_type, values=local_host_values or None) + elif local_host_values: + raise DemistoException("local_host_type is required when local_host_values is specified.") + + remote_port_type = args.get("remote_port_type") + remote_port_values_raw = argToList(args.get("remote_port_values")) + remote_port = None + if remote_port_type: + if remote_port_type != "any" and not remote_port_values_raw: + raise DemistoException("remote_port_values is required when remote_port_type is not 'any'.") + remote_port_values = [arg_to_number(p, arg_name="remote_port_values") for p in remote_port_values_raw] + remote_port = assign_params(type=remote_port_type, values=remote_port_values or None) + elif remote_port_values_raw: + raise DemistoException("remote_port_type is required when remote_port_values is specified.") + + local_port_type = args.get("local_port_type") + local_port_values_raw = argToList(args.get("local_port_values")) + local_port = None + if local_port_type: + if local_port_type != "any" and not local_port_values_raw: + raise DemistoException("local_port_values is required when local_port_type is not 'any'.") + local_port_values = [arg_to_number(p, arg_name="local_port_values") for p in local_port_values_raw] + local_port = assign_params(type=local_port_type, values=local_port_values or None) + elif local_port_values_raw: + raise DemistoException("local_port_type is required when local_port_values is specified.") + + application_type = args.get("application_type") + application_values = argToList(args.get("application_values")) + application = None + if application_type: + if application_type != "any" and not application_values: + raise DemistoException("application_values is required when application_type is not 'any'.") + application = assign_params(type=application_type, values=application_values or None) + elif application_values: + raise DemistoException("application_type is required when application_values is specified.") + + location_type = args.get("location_type") + location_ids = argToList(args.get("location_ids")) + location = None + if location_type: + 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 + location = assign_params(type=location_type, values=location_values) + elif location_ids: + raise DemistoException("location_type is required when location_ids is specified.") + + filter_data = assign_params( + tenant=tenant, + accountIds=account_ids, + siteIds=site_ids, + groupIds=group_ids, + ) + + rule_data = assign_params( + name=name, + action=action, + status=status, + direction=direction, + osTypes=os_types, + protocol=protocol, + description=description, + tagIds=tag_ids, + remoteHosts=remote_hosts, + localHost=local_host, + remotePort=remote_port, + localPort=local_port, + application=application, + location=location, + ) + + new_rule = client.create_firewall_rule_request(filter_data=filter_data, rule_data=rule_data) + + context_entry: dict = {} + title = "Sentinel One - Create Firewall Rule" + + if new_rule: + title += "\nThe firewall rule was successfully created." + 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(title, context_entry, removeNull=True, headerTransform=pascalToSpace), + outputs_prefix="SentinelOne.FirewallRule", + 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). + """ + tenant = argToBoolean(args["tenant"]) if args.get("tenant") is not None else None + filter_data = assign_params( + ids=argToList(args.get("ids")) or None, + accountIds=argToList(args.get("account_ids")) or None, + siteIds=argToList(args.get("site_ids")) or None, + groupIds=argToList(args.get("group_ids")) or None, + tenant=tenant, + 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 filter_data: + raise DemistoException("You must provide at least one filter parameter to delete firewall rules.") + + response = client.delete_firewall_rule_request(filter_data=filter_data) + affected = response.get("affected", 0) + + return CommandResults( + readable_output=f"Sentinel One - Firewall Rules Deleted\n{affected} firewall rule(s) successfully 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 +6832,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 a8d596730b49..6620ceb62101 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 (e.g., windows, macos, linux, windows_legacy). + isArray: true + name: os_types + predefined: + - linux + - windows + - macos + - windows_legacy + - description: The network protocol for the rule (e.g., any, tcp, udp, icmp). + name: protocol + - auto: PREDEFINED + description: The type of remote host to match (any, cidr, range, addresses, fqdn). 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 (e.g., IP addresses, CIDR blocks, IP ranges, or domains/FQDNs). + isArray: true + name: remote_host_values + - auto: PREDEFINED + description: The type of local host to match (any, cidr, range, addresses, fqdn). + 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 (any, ports, range). + 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 (any, ports, range). + 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 (any, path, sha1, system). + 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 (all, specific, fallback). + 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.ID + description: The created firewall rule ID. + type: string + - contextPath: SentinelOne.FirewallRule.Name + description: The name of the firewall rule. + type: string + - contextPath: SentinelOne.FirewallRule.Action + description: The rule action (Allow or Block). + type: string + - contextPath: SentinelOne.FirewallRule.Status + description: The status of the rule (Enabled or Disabled). + type: string + - contextPath: SentinelOne.FirewallRule.Direction + description: The direction of the firewall rule (any, inbound, outbound). + type: string + - contextPath: SentinelOne.FirewallRule.OsType + description: The primary OS type of the rule. + type: string + - contextPath: SentinelOne.FirewallRule.OsTypes + description: The list of OS types for the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Protocol + description: The protocol of the firewall rule. + type: string + - contextPath: SentinelOne.FirewallRule.RemoteHost + description: The first remote host in the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.RemoteHosts + description: The list of remote hosts in the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.LocalHost + description: The local host structure of the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.RemotePort + description: The remote ports structure of the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.LocalPort + description: The local ports structure of the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Application + description: The application structure of the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Location + description: The location associated with the rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Scope + description: The scope of the rule (global, account, group, site). + type: string + - contextPath: SentinelOne.FirewallRule.ScopeId + description: The group, site, or account ID depending on the scope. + type: string + - contextPath: SentinelOne.FirewallRule.Order + description: The position of the rule in the list of rules. + type: number + - contextPath: SentinelOne.FirewallRule.Editable + description: Whether the rule can be modified at this scope level. + type: boolean + - contextPath: SentinelOne.FirewallRule.Creator + description: The full name of the user who created the rule. + type: string + - contextPath: SentinelOne.FirewallRule.CreatorId + description: The ID of the user who created the rule. + type: string + - contextPath: SentinelOne.FirewallRule.CreatedAt + description: The date and time of rule creation. + type: date + - contextPath: SentinelOne.FirewallRule.UpdatedAt + description: The date and time of the last update. + type: date + - contextPath: SentinelOne.FirewallRule.Description + description: The description of the firewall rule. + type: string + - contextPath: SentinelOne.FirewallRule.RuleCategory + description: The rule category (firewall or network_quarantine). + type: string + - contextPath: SentinelOne.FirewallRule.TagIds + description: The list of tag IDs linked to this rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.TagNames + description: The list of tag names linked to this rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.Tags + description: The list of tag objects linked to this rule. + type: Unknown + - contextPath: SentinelOne.FirewallRule.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 (linux, windows, macos, windows_legacy). + 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 (any, inbound, outbound). + isArray: true + name: directions + predefined: + - any + - inbound + - outbound + - auto: PREDEFINED + description: A comma-separated list of actions to filter firewall rules by (Allow, Block). + isArray: true + name: actions + predefined: + - Allow + - Block + - auto: PREDEFINED + description: A comma-separated list of statuses to filter firewall rules by (Enabled, Disabled). + 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 699d8f6865ff..50dd4b2f655e 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py @@ -2865,3 +2865,225 @@ 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", "bad.example.org"]}], + "localHost": {"type": "cidr", "values": ["10.0.0.0/8"]}, + "remotePort": {"type": "ports", "values": [80, 443]}, + "localPort": {"type": "any"}, + "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="remote_host_values is required"): + sentinelone_v2.create_firewall_rule_command( + client, + {"name": "Rule", "action": "Block", "status": "Enabled", "tenant": "true", "remote_host_type": "fqdn"}, + ) + + +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 at least one filter parameter"): + sentinelone_v2.delete_firewall_rule_command(client, {}) diff --git a/Packs/SentinelOne/README.md b/Packs/SentinelOne/README.md index 82a3f5bc9c1e..b49cb9fa3325 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_17.md b/Packs/SentinelOne/ReleaseNotes/3_3_17.md new file mode 100644 index 000000000000..7cfaeb82c4c0 --- /dev/null +++ b/Packs/SentinelOne/ReleaseNotes/3_3_17.md @@ -0,0 +1,6 @@ +#### 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. diff --git a/Packs/SentinelOne/pack_metadata.json b/Packs/SentinelOne/pack_metadata.json index c92b26fd959b..34433c5f65e5 100644 --- a/Packs/SentinelOne/pack_metadata.json +++ b/Packs/SentinelOne/pack_metadata.json @@ -2,7 +2,7 @@ "name": "SentinelOne", "description": "Endpoint protection", "support": "partner", - "currentVersion": "3.3.16", + "currentVersion": "3.3.17", "author": "SentinelOne", "url": "https://www.sentinelone.com/support/", "email": "support@sentinelone.com", From 8e04eeed68d45482cdca0cfc2c6e68a38d0c780b Mon Sep 17 00:00:00 2001 From: "ioannis.panagopoulos" Date: Thu, 29 Oct 2026 14:49:40 +0200 Subject: [PATCH 2/5] updated range,fqdn and cidr type checking and payload creation --- .../SentinelOne-V2/SentinelOne-V2.py | 23 ++++++++++++++++++- .../SentinelOne-V2/SentinelOne-V2_test.py | 17 +++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py index aff59cb39a63..05433347e772 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py @@ -3938,7 +3938,14 @@ def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: if remote_host_type: if remote_host_type != "any" and not remote_host_values: raise DemistoException("remote_host_values is required when remote_host_type is not 'any'.") - remote_hosts = [assign_params(type=remote_host_type, values=remote_host_values or None)] + 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"): + remote_hosts = [{"type": remote_host_type, "values": [val]} for val in remote_host_values] + else: + remote_hosts = [assign_params(type=remote_host_type, values=remote_host_values or None)] elif remote_host_values: raise DemistoException("remote_host_type is required when remote_host_values is specified.") @@ -3948,6 +3955,12 @@ def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: if local_host_type: if local_host_type != "any" and 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}'.") local_host = assign_params(type=local_host_type, values=local_host_values or None) elif local_host_values: raise DemistoException("local_host_type is required when local_host_values is specified.") @@ -3958,6 +3971,10 @@ def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: if remote_port_type: if remote_port_type != "any" and not remote_port_values_raw: raise DemistoException("remote_port_values is required when remote_port_type is not 'any'.") + if remote_port_type == "range" and len(remote_port_values_raw) != 2: + raise DemistoException( + "remote_port_values must contain exactly 2 port numbers (start and end port) when remote_port_type is 'range'." + ) remote_port_values = [arg_to_number(p, arg_name="remote_port_values") for p in remote_port_values_raw] remote_port = assign_params(type=remote_port_type, values=remote_port_values or None) elif remote_port_values_raw: @@ -3969,6 +3986,10 @@ def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: if local_port_type: if local_port_type != "any" and not local_port_values_raw: raise DemistoException("local_port_values is required when local_port_type is not 'any'.") + if local_port_type == "range" and len(local_port_values_raw) != 2: + raise DemistoException( + "local_port_values must contain exactly 2 port numbers (start and end port) when local_port_type is 'range'." + ) local_port_values = [arg_to_number(p, arg_name="local_port_values") for p in local_port_values_raw] local_port = assign_params(type=local_port_type, values=local_port_values or None) elif local_port_values_raw: diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py index 50dd4b2f655e..e2f60b61e1ba 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py @@ -3015,7 +3015,10 @@ def test_create_firewall_rule_full_schema_and_validations(mocker, requests_mock) "name": "Block Domain and Ports", "action": "Block", "status": "Enabled", - "remoteHosts": [{"type": "fqdn", "values": ["malicious.example.com", "bad.example.org"]}], + "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]}, "localPort": {"type": "any"}, @@ -3037,6 +3040,18 @@ def test_create_firewall_rule_full_schema_and_validations(mocker, requests_mock) 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", + }, + ) def test_delete_firewall_rule(mocker, requests_mock): From 026b65051bbd60d3e7cf430a25b65e1ee61bc9f5 Mon Sep 17 00:00:00 2001 From: "ioannis.panagopoulos" Date: Thu, 5 Nov 2026 10:01:28 +0200 Subject: [PATCH 3/5] resolved conflicts and updated release notes --- Packs/SentinelOne/ReleaseNotes/3_3_18.md | 7 +++++++ Packs/SentinelOne/pack_metadata.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 Packs/SentinelOne/ReleaseNotes/3_3_18.md diff --git a/Packs/SentinelOne/ReleaseNotes/3_3_18.md b/Packs/SentinelOne/ReleaseNotes/3_3_18.md new file mode 100644 index 000000000000..f4d3dc8ec2b1 --- /dev/null +++ b/Packs/SentinelOne/ReleaseNotes/3_3_18.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 diff --git a/Packs/SentinelOne/pack_metadata.json b/Packs/SentinelOne/pack_metadata.json index 34433c5f65e5..d8a24334f772 100644 --- a/Packs/SentinelOne/pack_metadata.json +++ b/Packs/SentinelOne/pack_metadata.json @@ -2,7 +2,7 @@ "name": "SentinelOne", "description": "Endpoint protection", "support": "partner", - "currentVersion": "3.3.17", + "currentVersion": "3.3.18", "author": "SentinelOne", "url": "https://www.sentinelone.com/support/", "email": "support@sentinelone.com", From 60e13215bdf7376cf98e8fdff07f07a42f65f865 Mon Sep 17 00:00:00 2001 From: "ioannis.panagopoulos" Date: Wed, 11 Nov 2026 15:10:27 +0200 Subject: [PATCH 4/5] review changes --- .../Integrations/SentinelOne-V2/README.md | 86 +++--- .../SentinelOne-V2/SentinelOne-V2.py | 246 +++++++++--------- .../SentinelOne-V2/SentinelOne-V2.yml | 84 +++--- .../SentinelOne-V2/SentinelOne-V2_test.py | 45 +++- 4 files changed, 258 insertions(+), 203 deletions(-) diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md b/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md index a894b1fa562a..de534b49beba 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/README.md @@ -2515,19 +2515,19 @@ Creates a Firewall Control rule for a specified scope and OS to allow or block n | 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 (e.g., windows, macos, linux, windows_legacy). Possible values are: linux, windows, macos, windows_legacy. | Optional | -| protocol | The network protocol for the rule (e.g., any, tcp, udp, icmp). | Optional | -| remote_host_type | The type of remote host to match (any, cidr, range, addresses, fqdn). 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 (e.g., IP addresses, CIDR blocks, IP ranges, or domains/FQDNs). | Optional | -| local_host_type | The type of local host to match (any, cidr, range, addresses, fqdn). Possible values are: any, cidr, range, addresses, fqdn. | 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 (any, ports, range). Possible values are: any, ports, range. | 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 (any, ports, range). Possible values are: any, ports, range. | 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 (any, path, sha1, system). Possible values are: any, path, sha1, system. | 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 (all, specific, fallback). Possible values are: all, specific, fallback. | 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 | @@ -2540,35 +2540,35 @@ Creates a Firewall Control rule for a specified scope and OS to allow or block n | **Path** | **Type** | **Description** | | --- | --- | --- | -| SentinelOne.FirewallRule.ID | String | The created firewall rule ID. | -| SentinelOne.FirewallRule.Name | String | The name of the firewall rule. | -| SentinelOne.FirewallRule.Action | String | The rule action (Allow or Block). | -| SentinelOne.FirewallRule.Status | String | The status of the rule (Enabled or Disabled). | -| SentinelOne.FirewallRule.Direction | String | The direction of the firewall rule (any, inbound, outbound). | -| SentinelOne.FirewallRule.OsType | String | The primary OS type of the rule. | -| SentinelOne.FirewallRule.OsTypes | Unknown | The list of OS types for the rule. | -| SentinelOne.FirewallRule.Protocol | String | The protocol of the firewall rule. | -| SentinelOne.FirewallRule.RemoteHost | Unknown | The first remote host in the rule. | -| SentinelOne.FirewallRule.RemoteHosts | Unknown | The list of remote hosts in the rule. | -| SentinelOne.FirewallRule.LocalHost | Unknown | The local host structure of the rule. | -| SentinelOne.FirewallRule.RemotePort | Unknown | The remote ports structure of the rule. | -| SentinelOne.FirewallRule.LocalPort | Unknown | The local ports structure of the rule. | -| SentinelOne.FirewallRule.Application | Unknown | The application structure of the rule. | -| SentinelOne.FirewallRule.Location | Unknown | The location associated with the rule. | -| SentinelOne.FirewallRule.Scope | String | The scope of the rule (global, account, group, site). | -| SentinelOne.FirewallRule.ScopeId | String | The group, site, or account ID depending on the scope. | -| SentinelOne.FirewallRule.Order | Number | The position of the rule in the list of rules. | -| SentinelOne.FirewallRule.Editable | Boolean | Whether the rule can be modified at this scope level. | -| SentinelOne.FirewallRule.Creator | String | The full name of the user who created the rule. | -| SentinelOne.FirewallRule.CreatorId | String | The ID of the user who created the rule. | -| SentinelOne.FirewallRule.CreatedAt | Date | The date and time of rule creation. | -| SentinelOne.FirewallRule.UpdatedAt | Date | The date and time of the last update. | -| SentinelOne.FirewallRule.Description | String | The description of the firewall rule. | -| SentinelOne.FirewallRule.RuleCategory | String | The rule category (firewall or network_quarantine). | -| SentinelOne.FirewallRule.TagIds | Unknown | The list of tag IDs linked to this rule. | -| SentinelOne.FirewallRule.TagNames | Unknown | The list of tag names linked to this rule. | -| SentinelOne.FirewallRule.Tags | Unknown | The list of tag objects linked to this rule. | -| SentinelOne.FirewallRule.ProductId | String | The product identifier. | +| 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 @@ -2593,10 +2593,10 @@ Deletes Firewall Control rules that match the filter (uses the v2.1 firewall-con | 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 (linux, windows, macos, windows_legacy). Possible values are: linux, windows, macos, windows_legacy. | Optional | -| directions | A comma-separated list of directions to filter firewall rules by (any, inbound, outbound). Possible values are: any, inbound, outbound. | Optional | -| actions | A comma-separated list of actions to filter firewall rules by (Allow, Block). Possible values are: Allow, Block. | Optional | -| statuses | A comma-separated list of statuses to filter firewall rules by (Enabled, Disabled). Possible values are: Enabled, Disabled. | 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 | @@ -2614,4 +2614,4 @@ Deletes Firewall Control rules that match the filter (uses the v2.1 firewall-con | **Path** | **Type** | **Description** | | --- | --- | --- | -| SentinelOne.FirewallRule.Deleted.Affected | Number | The number of firewall rules successfully deleted. | +| 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 4b0cad872660..3fc358a82187 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py @@ -3909,135 +3909,150 @@ def delete_unified_exclusion_command(client: Client, args: dict) -> CommandResul ) -def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: - """ - Create a Firewall Control rule for a specified scope (v2.1 firewall-control endpoint). - """ - name = args["name"] - action = args["action"] - status = args["status"] - direction = args.get("direction") - os_types = argToList(args.get("os_types")) or None - protocol = args.get("protocol") - description = args.get("description") - tag_ids = argToList(args.get("tag_ids")) or None - +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 - if tenant is None and not any([account_ids, site_ids, group_ids]): + 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")) - remote_hosts = None - if remote_host_type: - if remote_host_type != "any" and 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"): - remote_hosts = [{"type": remote_host_type, "values": [val]} for val in remote_host_values] - else: - remote_hosts = [assign_params(type=remote_host_type, values=remote_host_values or None)] - elif remote_host_values: - raise DemistoException("remote_host_type is required when remote_host_values is specified.") + 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" and remote_host_values: + raise DemistoException("remote_host_values cannot be specified when remote_host_type is 'any'.") + if remote_host_type != "any" and 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 [assign_params(type=remote_host_type, values=remote_host_values or None)] + +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")) - local_host = None - if local_host_type: - if local_host_type != "any" and 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}'.") - local_host = assign_params(type=local_host_type, values=local_host_values or None) - elif local_host_values: - raise DemistoException("local_host_type is required when local_host_values is specified.") - - remote_port_type = args.get("remote_port_type") - remote_port_values_raw = argToList(args.get("remote_port_values")) - remote_port = None - if remote_port_type: - if remote_port_type != "any" and not remote_port_values_raw: - raise DemistoException("remote_port_values is required when remote_port_type is not 'any'.") - if remote_port_type == "range" and len(remote_port_values_raw) != 2: - raise DemistoException( - "remote_port_values must contain exactly 2 port numbers (start and end port) when remote_port_type is 'range'." - ) - remote_port_values = [arg_to_number(p, arg_name="remote_port_values") for p in remote_port_values_raw] - remote_port = assign_params(type=remote_port_type, values=remote_port_values or None) - elif remote_port_values_raw: - raise DemistoException("remote_port_type is required when remote_port_values is specified.") - - local_port_type = args.get("local_port_type") - local_port_values_raw = argToList(args.get("local_port_values")) - local_port = None - if local_port_type: - if local_port_type != "any" and not local_port_values_raw: - raise DemistoException("local_port_values is required when local_port_type is not 'any'.") - if local_port_type == "range" and len(local_port_values_raw) != 2: - raise DemistoException( - "local_port_values must contain exactly 2 port numbers (start and end port) when local_port_type is 'range'." - ) - local_port_values = [arg_to_number(p, arg_name="local_port_values") for p in local_port_values_raw] - local_port = assign_params(type=local_port_type, values=local_port_values or None) - elif local_port_values_raw: - raise DemistoException("local_port_type is required when local_port_values is specified.") + 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" and local_host_values: + raise DemistoException("local_host_values cannot be specified when local_host_type is 'any'.") + if local_host_type != "any" and 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 assign_params(type=local_host_type, values=local_host_values or None) + + +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" and port_values_raw: + raise DemistoException(f"{values_arg} cannot be specified when {type_arg} is 'any'.") + if port_type != "any" and 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 assign_params(type=port_type, values=port_values or None) + +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")) - application = None - if application_type: - if application_type != "any" and not application_values: - raise DemistoException("application_values is required when application_type is not 'any'.") - application = assign_params(type=application_type, values=application_values or None) - elif application_values: - raise DemistoException("application_type is required when application_values is specified.") + if not application_type: + if application_values: + raise DemistoException("application_type is required when application_values is specified.") + return None + if application_type == "any" and application_values: + raise DemistoException("application_values cannot be specified when application_type is 'any'.") + if application_type != "any" and not application_values: + raise DemistoException("application_values is required when application_type is not 'any'.") + return assign_params(type=application_type, values=application_values or None) + + +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")) - location = None - if location_type: - 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 - location = assign_params(type=location_type, values=location_values) - elif location_ids: - raise DemistoException("location_type is required when location_ids is specified.") - - filter_data = assign_params( - tenant=tenant, - accountIds=account_ids, - siteIds=site_ids, - groupIds=group_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=name, - action=action, - status=status, - direction=direction, - osTypes=os_types, - protocol=protocol, - description=description, - tagIds=tag_ids, - remoteHosts=remote_hosts, - localHost=local_host, - remotePort=remote_port, - localPort=local_port, - application=application, - location=location, + 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) @@ -4046,7 +4061,7 @@ def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: title = "Sentinel One - Create Firewall Rule" if new_rule: - title += "\nThe firewall rule was successfully created." + title += "The firewall rule was successfully created." context_entry = { "ID": new_rule.get("id"), "Name": new_rule.get("name"), @@ -4081,7 +4096,7 @@ def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: return CommandResults( readable_output=tableToMarkdown(title, context_entry, removeNull=True, headerTransform=pascalToSpace), - outputs_prefix="SentinelOne.FirewallRule", + outputs_prefix="SentinelOne.FirewallRule.Created", outputs_key_field="ID", outputs=context_entry, raw_response=new_rule, @@ -4092,13 +4107,9 @@ def delete_firewall_rule_command(client: Client, args: dict) -> CommandResults: """ Delete Firewall Control rules that match the filter (v2.1 firewall-control endpoint). """ - tenant = argToBoolean(args["tenant"]) if args.get("tenant") is not None else None - filter_data = assign_params( + scope_filter = build_firewall_rule_scope_filter(args) + rule_filter = assign_params( ids=argToList(args.get("ids")) or None, - accountIds=argToList(args.get("account_ids")) or None, - siteIds=argToList(args.get("site_ids")) or None, - groupIds=argToList(args.get("group_ids")) or None, - tenant=tenant, query=args.get("query"), scopes=argToList(args.get("scopes")) or None, applications=argToList(args.get("applications")) or None, @@ -4122,9 +4133,10 @@ def delete_firewall_rule_command(client: Client, args: dict) -> CommandResults: service__contains=argToList(args.get("service_contains")) or None, ) - if not filter_data: - raise DemistoException("You must provide at least one filter parameter to delete firewall rules.") + 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) diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml index 642f3941e685..65a5b8dedb26 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.yml @@ -847,7 +847,7 @@ script: - inbound - outbound - auto: PREDEFINED - description: A comma-separated list of OS types for the rule (e.g., windows, macos, linux, windows_legacy). + description: A comma-separated list of OS types for the rule. isArray: true name: os_types predefined: @@ -855,10 +855,10 @@ script: - windows - macos - windows_legacy - - description: The network protocol for the rule (e.g., any, tcp, udp, icmp). + - description: The network protocol for the rule. name: protocol - auto: PREDEFINED - description: The type of remote host to match (any, cidr, range, addresses, fqdn). Use 'addresses' for IP addresses and 'fqdn' for domains/FQDNs. + description: The type of remote host to match. Use 'addresses' for IP addresses and 'fqdn' for domains/FQDNs. name: remote_host_type predefined: - any @@ -866,11 +866,11 @@ script: - range - addresses - fqdn - - description: A comma-separated list of remote host values (e.g., IP addresses, CIDR blocks, IP ranges, or domains/FQDNs). + - 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 (any, cidr, range, addresses, fqdn). + description: The type of local host to match. name: local_host_type predefined: - any @@ -882,7 +882,7 @@ script: isArray: true name: local_host_values - auto: PREDEFINED - description: The type of remote ports to match (any, ports, range). + description: The type of remote ports to match. name: remote_port_type predefined: - any @@ -892,7 +892,7 @@ script: isArray: true name: remote_port_values - auto: PREDEFINED - description: The type of local ports to match (any, ports, range). + description: The type of local ports to match. name: local_port_type predefined: - any @@ -902,7 +902,7 @@ script: isArray: true name: local_port_values - auto: PREDEFINED - description: The type of application for the rule (any, path, sha1, system). + description: The type of application for the rule. name: application_type predefined: - any @@ -913,7 +913,7 @@ script: isArray: true name: application_values - auto: PREDEFINED - description: The location type for the rule (all, specific, fallback). + description: The location type for the rule. name: location_type predefined: - all @@ -945,91 +945,91 @@ script: 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.ID + - contextPath: SentinelOne.FirewallRule.Created.ID description: The created firewall rule ID. type: string - - contextPath: SentinelOne.FirewallRule.Name + - contextPath: SentinelOne.FirewallRule.Created.Name description: The name of the firewall rule. type: string - - contextPath: SentinelOne.FirewallRule.Action + - contextPath: SentinelOne.FirewallRule.Created.Action description: The rule action (Allow or Block). type: string - - contextPath: SentinelOne.FirewallRule.Status + - contextPath: SentinelOne.FirewallRule.Created.Status description: The status of the rule (Enabled or Disabled). type: string - - contextPath: SentinelOne.FirewallRule.Direction + - contextPath: SentinelOne.FirewallRule.Created.Direction description: The direction of the firewall rule (any, inbound, outbound). type: string - - contextPath: SentinelOne.FirewallRule.OsType + - contextPath: SentinelOne.FirewallRule.Created.OsType description: The primary OS type of the rule. type: string - - contextPath: SentinelOne.FirewallRule.OsTypes + - contextPath: SentinelOne.FirewallRule.Created.OsTypes description: The list of OS types for the rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.Protocol + - contextPath: SentinelOne.FirewallRule.Created.Protocol description: The protocol of the firewall rule. type: string - - contextPath: SentinelOne.FirewallRule.RemoteHost + - contextPath: SentinelOne.FirewallRule.Created.RemoteHost description: The first remote host in the rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.RemoteHosts + - contextPath: SentinelOne.FirewallRule.Created.RemoteHosts description: The list of remote hosts in the rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.LocalHost + - contextPath: SentinelOne.FirewallRule.Created.LocalHost description: The local host structure of the rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.RemotePort + - contextPath: SentinelOne.FirewallRule.Created.RemotePort description: The remote ports structure of the rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.LocalPort + - contextPath: SentinelOne.FirewallRule.Created.LocalPort description: The local ports structure of the rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.Application + - contextPath: SentinelOne.FirewallRule.Created.Application description: The application structure of the rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.Location + - contextPath: SentinelOne.FirewallRule.Created.Location description: The location associated with the rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.Scope + - contextPath: SentinelOne.FirewallRule.Created.Scope description: The scope of the rule (global, account, group, site). type: string - - contextPath: SentinelOne.FirewallRule.ScopeId + - contextPath: SentinelOne.FirewallRule.Created.ScopeId description: The group, site, or account ID depending on the scope. type: string - - contextPath: SentinelOne.FirewallRule.Order + - contextPath: SentinelOne.FirewallRule.Created.Order description: The position of the rule in the list of rules. type: number - - contextPath: SentinelOne.FirewallRule.Editable + - contextPath: SentinelOne.FirewallRule.Created.Editable description: Whether the rule can be modified at this scope level. type: boolean - - contextPath: SentinelOne.FirewallRule.Creator + - contextPath: SentinelOne.FirewallRule.Created.Creator description: The full name of the user who created the rule. type: string - - contextPath: SentinelOne.FirewallRule.CreatorId + - contextPath: SentinelOne.FirewallRule.Created.CreatorId description: The ID of the user who created the rule. type: string - - contextPath: SentinelOne.FirewallRule.CreatedAt + - contextPath: SentinelOne.FirewallRule.Created.CreatedAt description: The date and time of rule creation. type: date - - contextPath: SentinelOne.FirewallRule.UpdatedAt + - contextPath: SentinelOne.FirewallRule.Created.UpdatedAt description: The date and time of the last update. type: date - - contextPath: SentinelOne.FirewallRule.Description + - contextPath: SentinelOne.FirewallRule.Created.Description description: The description of the firewall rule. type: string - - contextPath: SentinelOne.FirewallRule.RuleCategory + - contextPath: SentinelOne.FirewallRule.Created.RuleCategory description: The rule category (firewall or network_quarantine). type: string - - contextPath: SentinelOne.FirewallRule.TagIds + - contextPath: SentinelOne.FirewallRule.Created.TagIds description: The list of tag IDs linked to this rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.TagNames + - contextPath: SentinelOne.FirewallRule.Created.TagNames description: The list of tag names linked to this rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.Tags + - contextPath: SentinelOne.FirewallRule.Created.Tags description: The list of tag objects linked to this rule. type: Unknown - - contextPath: SentinelOne.FirewallRule.ProductId + - contextPath: SentinelOne.FirewallRule.Created.ProductId description: The product identifier. type: string - arguments: @@ -1071,7 +1071,7 @@ script: isArray: true name: protocols - auto: PREDEFINED - description: A comma-separated list of OS types to filter firewall rules by (linux, windows, macos, windows_legacy). + description: A comma-separated list of OS types to filter firewall rules by. isArray: true name: os_types predefined: @@ -1080,7 +1080,7 @@ script: - macos - windows_legacy - auto: PREDEFINED - description: A comma-separated list of directions to filter firewall rules by (any, inbound, outbound). + description: A comma-separated list of directions to filter firewall rules by. isArray: true name: directions predefined: @@ -1088,14 +1088,14 @@ script: - inbound - outbound - auto: PREDEFINED - description: A comma-separated list of actions to filter firewall rules by (Allow, Block). + 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 (Enabled, Disabled). + description: A comma-separated list of statuses to filter firewall rules by. isArray: true name: statuses predefined: diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py index a91ec200bf56..80060f59f18e 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py @@ -3075,6 +3075,28 @@ def test_create_firewall_rule_full_schema_and_validations(mocker, requests_mock) 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, @@ -3092,6 +3114,18 @@ def test_create_firewall_rule_full_schema_and_validations(mocker, requests_mock) "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): @@ -3140,5 +3174,14 @@ def test_delete_firewall_rule(mocker, requests_mock): } 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 at least one filter parameter"): + 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"}) From 8e40f9a966bf31409a20e37d8430b8f7404e9181 Mon Sep 17 00:00:00 2001 From: "ioannis.panagopoulos" Date: Wed, 11 Nov 2026 17:01:06 +0200 Subject: [PATCH 5/5] code improvements --- .../SentinelOne-V2/SentinelOne-V2.py | 49 +++++++++++-------- .../SentinelOne-V2/SentinelOne-V2_test.py | 1 - 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py index 3fc358a82187..50b25e47fa68 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2.py @@ -3941,9 +3941,11 @@ def build_remote_hosts(args: dict) -> list[dict] | None: raise DemistoException("remote_host_type is required when remote_host_values is specified.") return None - if remote_host_type == "any" and remote_host_values: - raise DemistoException("remote_host_values cannot be specified when remote_host_type is 'any'.") - if remote_host_type != "any" and not remote_host_values: + 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( @@ -3951,7 +3953,7 @@ def build_remote_hosts(args: dict) -> list[dict] | None: ) if remote_host_type in ("fqdn", "cidr"): return [{"type": remote_host_type, "values": [val]} for val in remote_host_values] - return [assign_params(type=remote_host_type, values=remote_host_values or None)] + return [{"type": remote_host_type, "values": remote_host_values}] def build_local_host(args: dict) -> dict | None: @@ -3963,9 +3965,11 @@ def build_local_host(args: dict) -> dict | None: raise DemistoException("local_host_type is required when local_host_values is specified.") return None - if local_host_type == "any" and local_host_values: - raise DemistoException("local_host_values cannot be specified when local_host_type is 'any'.") - if local_host_type != "any" and not local_host_values: + 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( @@ -3973,7 +3977,7 @@ def build_local_host(args: dict) -> dict | None: ) 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 assign_params(type=local_host_type, values=local_host_values or None) + return {"type": local_host_type, "values": local_host_values} def build_firewall_rule_port(args: dict, prefix: str) -> dict | None: @@ -3987,16 +3991,18 @@ def build_firewall_rule_port(args: dict, prefix: str) -> dict | None: raise DemistoException(f"{type_arg} is required when {values_arg} is specified.") return None - if port_type == "any" and port_values_raw: - raise DemistoException(f"{values_arg} cannot be specified when {type_arg} is 'any'.") - if port_type != "any" and not port_values_raw: + 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 assign_params(type=port_type, values=port_values or None) + return {"type": port_type, "values": port_values} def build_firewall_rule_application(args: dict) -> dict | None: @@ -4008,11 +4014,13 @@ def build_firewall_rule_application(args: dict) -> dict | None: raise DemistoException("application_type is required when application_values is specified.") return None - if application_type == "any" and application_values: - raise DemistoException("application_values cannot be specified when application_type is 'any'.") - if application_type != "any" and not application_values: + 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 assign_params(type=application_type, values=application_values or None) + return {"type": application_type, "values": application_values} def build_firewall_rule_location(args: dict) -> dict | None: @@ -4058,10 +4066,7 @@ def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: new_rule = client.create_firewall_rule_request(filter_data=filter_data, rule_data=rule_data) context_entry: dict = {} - title = "Sentinel One - Create Firewall Rule" - if new_rule: - title += "The firewall rule was successfully created." context_entry = { "ID": new_rule.get("id"), "Name": new_rule.get("name"), @@ -4095,7 +4100,9 @@ def create_firewall_rule_command(client: Client, args: dict) -> CommandResults: } return CommandResults( - readable_output=tableToMarkdown(title, context_entry, removeNull=True, headerTransform=pascalToSpace), + 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, @@ -4141,7 +4148,7 @@ def delete_firewall_rule_command(client: Client, args: dict) -> CommandResults: affected = response.get("affected", 0) return CommandResults( - readable_output=f"Sentinel One - Firewall Rules Deleted\n{affected} firewall rule(s) successfully deleted.", + readable_output="Sentinel One - Firewall Rules Deleted.", outputs_prefix="SentinelOne.FirewallRule.Deleted", outputs={"Affected": affected}, raw_response=response, diff --git a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py index 80060f59f18e..63fe42b62cc3 100644 --- a/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py +++ b/Packs/SentinelOne/Integrations/SentinelOne-V2/SentinelOne-V2_test.py @@ -3061,7 +3061,6 @@ def test_create_firewall_rule_full_schema_and_validations(mocker, requests_mock) ], "localHost": {"type": "cidr", "values": ["10.0.0.0/8"]}, "remotePort": {"type": "ports", "values": [80, 443]}, - "localPort": {"type": "any"}, "application": {"type": "path", "values": ["C:\\bad.exe"]}, "location": {"type": "specific", "values": [{"id": "225494730938493804"}]}, "tagIds": ["225494730938493915"],