From 5578989d30a9cc98b391d6b30a518d42e3654686 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Fri, 2 Sep 2022 21:59:08 -0600 Subject: [PATCH] feat: tcpflags, quick, state type in rules (#272) * feat: support statetype is fw rules * fix: support quick apply rules * feat: support tcpflags * test: fixed synproxy gateway test * feat: support openvpn and multi-if rules * fix: corrected rule interface validation * test: correct return id for rule gw test * docs: added docs to detail new fields --- .../etc/inc/api/framework/APIResponse.inc | 36 +++ .../files/etc/inc/api/framework/APITools.inc | 7 +- .../inc/api/models/APIFirewallRuleCreate.inc | 159 +++++++++- .../inc/api/models/APIFirewallRuleUpdate.inc | 194 +++++++++++- .../local/www/api/documentation/openapi.yml | 64 +++- tests/test_api_v1_firewall_rule.py | 292 +++++++++++++++++- 6 files changed, 720 insertions(+), 32 deletions(-) diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc index 054c32501..6f02c9309 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc @@ -4107,6 +4107,42 @@ function get($id, $data=[], $all=false) { "return" => $id, "message" => "Alias resolve interval must be numeric" ], + 4243 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Unknown firewall rule state type specified" + ], + 4244 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Firewall rule protocol must be 'tcp' if state type is set to synproxy" + ], + 4245 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Firewall rule gateway must be default if state type is set to synproxy" + ], + 4246 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Unknown firewall rule TCP flag type specified" + ], + 4247 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Firewall rule tcpflags1 (set) items must also be present in tcpflags2 (out of)" + ], + 4248 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Firewall rules can only be applied to one interface unless it is a floating rule" + ], //5000-5999 reserved for /api/v1/user API calls 5000 => [ diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc b/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc index dee9a4934..66d503be9 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc @@ -359,7 +359,7 @@ function sort_nat_rules($top=false, $data=null, $field=null) { } # Input a physical interface ID, or a descriptive interface name, and return the pfSense interface ID (lan,wan,optx) -function get_pfsense_if_id($interface, $include_carp=false, $include_ifgroup=false, $include_ipsec=false) { +function get_pfsense_if_id($interface, $include_carp=false, $include_ifgroup=false, $include_ipsec=false, $include_openvpn=false) { # Variables global $config; # Loop through our config and check each interface for a physical ID match @@ -402,6 +402,11 @@ function get_pfsense_if_id($interface, $include_carp=false, $include_ifgroup=fal if ($include_ipsec and $interface === "enc0") { return $interface; } + + # Only include the OpenVPN interface if explicitly requested and OpenVPN is configured + if ($include_openvpn and $interface === "openvpn" and $config["openvpn"]) { + return $interface; + } } # Check if a string is a virtual IPs unique ID diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleCreate.inc index 147bac568..9445078a4 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleCreate.inc @@ -17,6 +17,8 @@ require_once("api/framework/APIModel.inc"); require_once("api/framework/APIResponse.inc"); class APIFirewallRuleCreate extends APIModel { + public $tcpflag_options = ["fin", "syn", "rst", "psh", "ack", "urg", "ece", "cwr"]; + # Create our method constructor public function __construct() { parent::__construct(); @@ -56,13 +58,37 @@ class APIFirewallRuleCreate extends APIModel { private function __validate_interface() { # Check for our required 'interface' payload value if (isset($this->initial_data['interface'])) { - $this->initial_data['interface'] = APITools\get_pfsense_if_id($this->initial_data['interface'], false, true, true); - # Check that we found the request pfSense interface ID - if (is_string($this->initial_data["interface"])) { - $this->validated_data['interface'] = $this->initial_data['interface']; - } else { - $this->errors[] = APIResponse\get(4034); + # Convert value to array if it is not already and support comma separated strings. + if (!is_array($this->initial_data["interface"])) { + $this->initial_data["interface"] = explode(",", (string)$this->initial_data["interface"]); + } + + # Only allow 1 interface to be specified unless this is a floating rule + if (!isset($this->validated_data["floating"]) and count($this->initial_data["interface"]) > 1) { + $this->errors[] = APIResponse\get(4248); + return; } + + # Initialize an array to store validated interfaces + $this->validated_data["interface"] = []; + + # Loop through each specified interface and ensure it exists + foreach ($this->initial_data["interface"] as $if) { + # Find this interface's pfSense interface ID + $if = APITools\get_pfsense_if_id($if, false, true, true, true); + + # Check that we found the request pfSense interface ID + if ($if) { + $this->validated_data["interface"][] = $if; + } else { + $this->errors[] = APIResponse\get(4040); + return; + } + } + + # Remove duplicates and convert back to pfSense expected format + $this->validated_data["interface"] = array_unique($this->validated_data["interface"]); + $this->validated_data["interface"] = implode(",", $this->validated_data["interface"]); } else { $this->errors[] = APIResponse\get(4034); } @@ -339,6 +365,111 @@ class APIFirewallRuleCreate extends APIModel { } } + private function __validate_tcpflags_any() { + # Check for our optional 'tcpflags_any' payload value + if ($this->initial_data['tcpflags_any'] === true) { + $this->validated_data["tcpflags_any"] = true; + } + } + + private function __validate_tcpflags2() { + # Only validate this field if 'tcpflags_any' is not set + if (!$this->validated_data["tcpflags_any"]) { + # Validate the optional 'tcpflags2' field + if (isset($this->initial_data["tcpflags2"])) { + # Initialize validated flags as empty array + $this->validated_data["tcpflags2"] = []; + + # Ensure value is an array and support comma separated strings + if (!is_array($this->initial_data["tcpflags2"])) { + $this->initial_data["tcpflags2"] = explode(",", (string)$this->initial_data["tcpflags2"]); + } + + # Loop through the specified TCP flags + foreach ($this->initial_data["tcpflags2"] as $tcpflag) { + # Ensure value is a valid tcp flag option + if (in_array($tcpflag, $this->tcpflag_options)) { + $this->validated_data["tcpflags2"][] = $tcpflag; + } + else { + $this->errors[] = APIResponse\get(4246); + return; + } + } + + # Remove duplicate items if present and convert to csv as expected by pfSense + $this->validated_data["tcpflags2"] = array_unique($this->validated_data["tcpflags2"]); + $this->validated_data["tcpflags2"] = implode(",", $this->validated_data["tcpflags2"]); + } + } + } + + private function __validate_tcpflags1() { + # Only validate this field if 'tcpflags_any' is not set + if (!$this->validated_data["tcpflags_any"]) { + # Validate the optional 'tcpflags1' field + if (isset($this->initial_data["tcpflags1"])) { + # Initialize validated flags as empty array + $this->validated_data["tcpflags1"] = []; + + # Ensure value is an array and support comma separated strings + if (!is_array($this->initial_data["tcpflags1"])) { + $this->initial_data["tcpflags1"] = explode(",", (string)$this->initial_data["tcpflags1"]); + } + + # Loop through the specified TCP flags + foreach ($this->initial_data["tcpflags1"] as $tcpflag) { + # Ensure value is a valid tcp flag option + if (in_array($tcpflag, $this->tcpflag_options)) { + # Ensure this value is also present in tcpflags2 (out of) + if (in_array($tcpflag, explode(",", $this->validated_data["tcpflags2"]))) { + $this->validated_data["tcpflags1"][] = $tcpflag; + } + else { + $this->errors[] = APIResponse\get(4247); + return; + } + } + else { + $this->errors[] = APIResponse\get(4246); + return; + } + } + + # Remove duplicate items if present and convert to csv as expected by pfSense + $this->validated_data["tcpflags1"] = array_unique($this->validated_data["tcpflags1"]); + $this->validated_data["tcpflags1"] = implode(",", $this->validated_data["tcpflags1"]); + } + } + } + + private function __validate_statetype() { + # Variables + $protocol = $this->validated_data["protocol"]; + $gateway = $this->validated_data["gateway"]; + $statetype_options = ["keep state", "sloppy state", "synproxy state"]; + + # Check for the optional 'statetype' field + if (isset($this->initial_data["statetype"])) { + # Require value to be supported state type + if (!in_array($this->initial_data["statetype"], $statetype_options)) { + $this->errors[] = APIResponse\get(4243); + } + # Require protocol to be 'tcp' if synproxy state is specified + elseif ($this->initial_data["statetype"] === "synproxy state" and $protocol !== "tcp") { + $this->errors[] = APIResponse\get(4244); + } + # Require the default gateway to be used if synproxy state is specified + elseif ($this->initial_data["statetype"] === "synproxy state" and $gateway) { + $this->errors[] = APIResponse\get(4245); + } + # Otherwise this value is valid. + else { + $this->validated_data["statetype"] = $this->initial_data["statetype"]; + } + } + } + private function __validate_top() { # Check for our optional 'top' payload value if ($this->initial_data['top'] === true) { @@ -368,7 +499,17 @@ class APIFirewallRuleCreate extends APIModel { } } + private function __validate_quick() { + # Only validate this field if floating is enabled + if ($this->validated_data["floating"]) { + if ($this->initial_data["quick"] === true) { + $this->validated_data["quick"] = "yes"; + } + } + } + public function validate_payload($delay_tracker=true) { + $this->__validate_floating(); $this->__validate_type(); $this->__validate_interface(); $this->__validate_ipprotocol(); @@ -387,9 +528,13 @@ class APIFirewallRuleCreate extends APIModel { $this->__validate_disabled(); $this->__validate_descr(); $this->__validate_log(); + $this->__validate_tcpflags_any(); + $this->__validate_tcpflags2(); + $this->__validate_tcpflags1(); + $this->__validate_statetype(); $this->__validate_top(); - $this->__validate_floating(); $this->__validate_direction(); + $this->__validate_quick(); # Delay generating the tracker. Reduces the likelihood of two rules getting the same tracker in looped calls. # todo: this is a quick fix and still does not guarantee uniqueness, a better solution is needed diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleUpdate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleUpdate.inc index 2d5ce3704..a03d5baa1 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleUpdate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleUpdate.inc @@ -17,6 +17,7 @@ require_once("api/framework/APIModel.inc"); require_once("api/framework/APIResponse.inc"); class APIFirewallRuleUpdate extends APIModel { + public $tcpflag_options = ["fin", "syn", "rst", "psh", "ack", "urg", "ece", "cwr"]; private $requires_port; private $missing_port; @@ -63,6 +64,16 @@ class APIFirewallRuleUpdate extends APIModel { } } + private function __validate_floating() { + # Check for floating rule + if ($this->initial_data["floating"] === true) { + $this->validated_data["floating"] = "yes"; + } elseif ($this->initial_data["floating"] === false) { + unset($this->validated_data["floating"]); + unset($this->validated_data["direction"]); + } + } + private function __validate_type() { # Check for our optional 'type' payload value if (isset($this->initial_data["type"])) { @@ -77,15 +88,42 @@ class APIFirewallRuleUpdate extends APIModel { } private function __validate_interface() { + # Always revalidate this field in case 'floating' was changed. + $this->initial_data["interface"] = ($this->initial_data["interface"]) ?: $this->validated_data["interface"]; + # Check for our optional 'interface' payload value if (isset($this->initial_data['interface'])) { - $this->initial_data['interface'] = APITools\get_pfsense_if_id($this->initial_data['interface'], false, true, true); - # Check that we found the request pfSense interface ID - if (is_string($this->initial_data["interface"])) { - $this->validated_data['interface'] = $this->initial_data['interface']; - } else { - $this->errors[] = APIResponse\get(4034); + # Convert value to array if it is not already and support comma separated strings. + if (!is_array($this->initial_data["interface"])) { + $this->initial_data["interface"] = explode(",", (string)$this->initial_data["interface"]); } + + # Only allow 1 interface to be specified unless this is a floating rule + if (!isset($this->validated_data["floating"]) and count($this->initial_data["interface"]) > 1) { + $this->errors[] = APIResponse\get(4248); + return; + } + + # Initialize an array to store validated interfaces + $this->validated_data["interface"] = []; + + # Loop through each specified interface and ensure it exists + foreach ($this->initial_data["interface"] as $if) { + # Find this interface's pfSense interface ID + $if = APITools\get_pfsense_if_id($if, false, true, true, true); + + # Check that we found the request pfSense interface ID + if ($if) { + $this->validated_data["interface"][] = $if; + } else { + $this->errors[] = APIResponse\get(4040); + return; + } + } + + # Remove duplicates and convert back to pfSense expected format + $this->validated_data["interface"] = array_unique($this->validated_data["interface"]); + $this->validated_data["interface"] = implode(",", $this->validated_data["interface"]); } } @@ -400,6 +438,121 @@ class APIFirewallRuleUpdate extends APIModel { } } + private function __validate_tcpflags_any() { + # Check for our optional 'tcpflags_any' payload value + if ($this->initial_data['tcpflags_any'] === true) { + $this->validated_data["tcpflags_any"] = true; + unset($this->validated_data["tcpflags1"]); + unset($this->validated_data["tcpflags2"]); + } + # Allow this field to be unset + elseif ($this->initial_data['tcpflags_any'] === false) { + unset($this->validated_data["tcpflags_any"]); + } + } + + private function __validate_tcpflags2() { + # Only validate this field if 'tcpflags_any' is not set + if (!$this->validated_data["tcpflags_any"]) { + # Validate the optional 'tcpflags2' field + if (isset($this->initial_data["tcpflags2"])) { + # Initialize validated flags as empty array + $this->validated_data["tcpflags2"] = []; + + # Ensure value is an array and support comma separated strings + if (!is_array($this->initial_data["tcpflags2"])) { + $this->initial_data["tcpflags2"] = explode(",", (string)$this->initial_data["tcpflags2"]); + } + + # Loop through the specified TCP flags + foreach ($this->initial_data["tcpflags2"] as $tcpflag) { + # Ensure value is a valid tcp flag option + if (in_array($tcpflag, $this->tcpflag_options)) { + $this->validated_data["tcpflags2"][] = $tcpflag; + } + else { + $this->errors[] = APIResponse\get(4246); + return; + } + } + + # Remove duplicate items if present and convert to csv as expected by pfSense + $this->validated_data["tcpflags2"] = array_unique($this->validated_data["tcpflags2"]); + $this->validated_data["tcpflags2"] = implode(",", $this->validated_data["tcpflags2"]); + } + } + } + + private function __validate_tcpflags1() { + # Only validate this field if 'tcpflags_any' is not set + if (!$this->validated_data["tcpflags_any"]) { + # Validate the optional 'tcpflags1' field + if (isset($this->initial_data["tcpflags1"])) { + # Initialize validated flags as empty array + $this->validated_data["tcpflags1"] = []; + + # Ensure value is an array and support comma separated strings + if (!is_array($this->initial_data["tcpflags1"])) { + $this->initial_data["tcpflags1"] = explode(",", (string)$this->initial_data["tcpflags1"]); + } + + # Loop through the specified TCP flags + foreach ($this->initial_data["tcpflags1"] as $tcpflag) { + # Ensure value is a valid tcp flag option + if (in_array($tcpflag, $this->tcpflag_options)) { + # Ensure this value is also present in tcpflags2 (out of) + if (in_array($tcpflag, explode(",", $this->validated_data["tcpflags2"]))) { + $this->validated_data["tcpflags1"][] = $tcpflag; + } + else { + $this->errors[] = APIResponse\get(4247); + return; + } + } + else { + $this->errors[] = APIResponse\get(4246); + return; + } + } + + # Remove duplicate items if present and convert to csv as expected by pfSense + $this->validated_data["tcpflags1"] = array_unique($this->validated_data["tcpflags1"]); + $this->validated_data["tcpflags1"] = implode(",", $this->validated_data["tcpflags1"]); + } + } + } + + private function __validate_statetype() { + # Variables + $protocol = $this->validated_data["protocol"]; + $gateway = $this->validated_data["gateway"]; + $statetype_options = ["keep state", "sloppy state", "synproxy state"]; + + # Check for the optional 'statetype' field + if (isset($this->initial_data["statetype"])) { + # Allow this value to be unset with an empty string + if ($this->initial_data["statetype"] === "") { + unset($this->validated_data["statetype"]); + } + # Otherwise, require value to be supported state type + elseif (!in_array($this->initial_data["statetype"], $statetype_options)) { + $this->errors[] = APIResponse\get(4243); + } + # Require protocol to be 'tcp' if synproxy state is specified + elseif ($this->initial_data["statetype"] === "synproxy state" and $protocol !== "tcp") { + $this->errors[] = APIResponse\get(4244); + } + # Require the default gateway to be used if synproxy state is specified + elseif ($this->initial_data["statetype"] === "synproxy state" and $gateway) { + $this->errors[] = APIResponse\get(4245); + } + # Otherwise this value is valid. + else { + $this->validated_data["statetype"] = $this->initial_data["statetype"]; + } + } + } + private function __validate_top() { # Check for our optional 'top' payload value if ($this->initial_data['top'] === true) { @@ -407,16 +560,6 @@ class APIFirewallRuleUpdate extends APIModel { } } - private function __validate_floating() { - # Check for floating rule - if ($this->initial_data["floating"] === true) { - $this->validated_data["floating"] = "yes"; - } elseif ($this->initial_data["floating"] === false) { - unset($this->validated_data["floating"]); - unset($this->validated_data["direction"]); - } - } - private function __validate_direction() { # Validate our optional 'floating' payload value if (!empty($this->validated_data["floating"])) { @@ -433,8 +576,21 @@ class APIFirewallRuleUpdate extends APIModel { } } + private function __validate_quick() { + # Only validate this field if floating is enabled + if ($this->validated_data["floating"]) { + if ($this->initial_data["quick"] === true) { + $this->validated_data["quick"] = "yes"; + } + elseif ($this->initial_data["quick"] === false) { + unset($this->validated_data["quick"]); + } + } + } + public function validate_payload() { $this->__validate_tracker(); + $this->__validate_floating(); $this->__validate_type(); $this->__validate_interface(); $this->__validate_ipprotocol(); @@ -453,9 +609,13 @@ class APIFirewallRuleUpdate extends APIModel { $this->__validate_disabled(); $this->__validate_descr(); $this->__validate_log(); + $this->__validate_tcpflags_any(); + $this->__validate_tcpflags2(); + $this->__validate_tcpflags1(); + $this->__validate_statetype(); $this->__validate_top(); - $this->__validate_floating(); $this->__validate_direction(); + $this->__validate_quick(); # Update our 'updated' value $this->validated_data["updated"] = [ diff --git a/pfSense-pkg-API/files/usr/local/www/api/documentation/openapi.yml b/pfSense-pkg-API/files/usr/local/www/api/documentation/openapi.yml index c0e3ebd38..da5cdf13d 100644 --- a/pfSense-pkg-API/files/usr/local/www/api/documentation/openapi.yml +++ b/pfSense-pkg-API/files/usr/local/www/api/documentation/openapi.yml @@ -2202,7 +2202,10 @@ paths: description: Interface this rule will apply to. You may specify either the interface's descriptive name, the pfSense interface ID (e.g. wan, lan, optx), or the real interface ID (e.g. igb0). - type: string + If `floating` is enabled, multiple interfaces may be specified. + type: array + items: + type: string ipprotocol: description: IP protocol(s) this rule will apply to. enum: @@ -2239,6 +2242,10 @@ paths: - carp - pfsync type: string + quick: + description: Apply action immediately upon match. This field is only available for `floating` rules. + type: boolean + default: false sched: description: Firewall schedule to apply to this rule. This must be an existing firewall schedule name. @@ -2259,6 +2266,30 @@ paths: port. This parameter is required when `protocol` is set to `tcp`, `udp`, or `tcp/udp`. type: string + statetype: + description: State type to use when this rule is matched. + type: string + enum: + - keep state + - sloppy state + - synproxy state + tcpflags_any: + description: Allow all TCP flags. This field is only relevant for TCP rules and will remove any + existing values in `tcpflags1` and `tcpflags2`. + type: boolean + default: false + tcpflags1: + description: TCP flags (set). Values specified must also be specified in `tcpflags2`. + type: array + items: + type: string + enum: ["fin", "syn", "rst", "psh", "ack", "urg", "ece", "cwr"] + tcpflags2: + description: TCP flags (out of). + type: array + items: + type: string + enum: ["fin", "syn", "rst", "psh", "ack", "urg", "ece", "cwr"] top: default: false description: Place this firewall rule at the top of the access control @@ -2397,7 +2428,10 @@ paths: description: Interface this rule will apply to. You may specify either the interface's descriptive name, the pfSense interface ID (e.g. wan, lan, optx), or the real interface ID (e.g. igb0). - type: string + If `floating` is enabled, multiple interfaces may be specified. + type: array + items: + type: string ipprotocol: description: IP protocol(s) this rule will apply to. enum: @@ -2433,6 +2467,9 @@ paths: - carp - pfsync type: string + quick: + description: Apply action immediately upon match. This field is only available for `floating` rules. + type: boolean sched: description: Firewall schedule to apply to this rule. This must be an existing firewall schedule name. @@ -2453,6 +2490,29 @@ paths: port. This parameter is required when `protocol` is set to `tcp`, `udp`, or `tcp/udp`. type: string + statetype: + description: State type to use when this rule is matched. + type: string + enum: + - keep state + - sloppy state + - synproxy state + tcpflags_any: + description: Allow all TCP flags. This field is only relevant for TCP rules and will remove any + existing values in `tcpflags1` and `tcpflags2`. + type: boolean + tcpflags1: + description: TCP flags (set). Values specified must also be specified in `tcpflags2`. + type: array + items: + type: string + enum: ["fin", "syn", "rst", "psh", "ack", "urg", "ece", "cwr"] + tcpflags2: + description: TCP flags (out of). + type: array + items: + type: string + enum: ["fin", "syn", "rst", "psh", "ack", "urg", "ece", "cwr"] top: default: false description: Place this firewall rule at the top of the access control diff --git a/tests/test_api_v1_firewall_rule.py b/tests/test_api_v1_firewall_rule.py index a8fb2f107..026871d63 100644 --- a/tests/test_api_v1_firewall_rule.py +++ b/tests/test_api_v1_firewall_rule.py @@ -139,7 +139,7 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest): "name": "Create floating firewall rule", "payload": { "type": "block", - "interface": "wan", + "interface": "wan,lan", "ipprotocol": "inet", "protocol": "tcp/udp", "src": "172.16.77.121", @@ -175,7 +175,7 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest): { "name": "Test interface validation", "status": 400, - "return": 4034, + "return": 4040, "payload": { "type": "pass", "interface": "INVALID" @@ -515,9 +515,158 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest): "srcport": "any", "dstport": "any", "floating": True, - "direction": "Test_Direction" + "direction": "INVALID" + } + }, + { + "name": "Check statetype options constraint", + "status": 400, + "return": 4243, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "statetype": "INVALID" + } + }, + { + "name": "Check protocol must be 'tcp' when statetype is 'synproxy state' constraint", + "status": 400, + "return": 4244, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": ["syn"], + "statetype": "synproxy state" + } + }, + { + "name": "Check gateway must be default when statetype is 'synproxy state' constraint", + "status": 400, + "return": 4245, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": "syn,fin", + "statetype": "synproxy state", + "gateway": "WAN_DHCP" } }, + { + "name": "Check tcpflags2 (out of) options constraint", + "status": 400, + "return": 4246, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": ["INVALID"] + } + }, + { + "name": "Check tcpflags2 (out of) options constraint (CSV)", + "status": 400, + "return": 4246, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": "syn,INVALID" + } + }, + { + "name": "Check tcpflags1 (set) options constraint", + "status": 400, + "return": 4246, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": ["syn"], + "tcpflags1": ["INVALID"] + } + }, + { + "name": "Check tcpflags1 (set) options constraint (CSV)", + "status": 400, + "return": 4246, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": ["syn"], + "tcpflags1": ["INVALID"] + } + }, + { + "name": "Check tcpflags1 (set) must be in tcpflags2 (out of) constraint", + "status": 400, + "return": 4247, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": ["syn"], + "tcpflags1": ["fin"] + } + }, + { + "name": "Check non-floating rules must have 1 interface constraint", + "status": 400, + "return": 4248, + "payload": { + "type": "pass", + "interface": ["wan", "lan"], + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any" + } + } ] put_tests = [ { @@ -543,7 +692,8 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest): "name": "Update floating firewall rule", "payload": { "type": "block", - "interface": "wan", + "floating": True, + "interface": ["wan", "lan"], "ipprotocol": "inet", "protocol": "tcp/udp", "src": "172.16.77.121", @@ -579,7 +729,7 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest): { "name": "Test interface validation", "status": 400, - "return": 4034, + "return": 4040, "payload": { "type": "pass", "interface": "INVALID" @@ -720,6 +870,138 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest): "direction": "Test_Direction" } }, + { + "name": "Check statetype options constraint", + "status": 400, + "return": 4243, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "statetype": "INVALID" + } + }, + { + "name": "Check protocol must be 'tcp' when statetype is 'synproxy state' constraint", + "status": 400, + "return": 4244, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "statetype": "synproxy state" + } + }, + { + "name": "Check gateway must be default when statetype is 'synproxy state' constraint", + "status": 400, + "return": 4245, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "statetype": "synproxy state", + "gateway": "WAN_DHCP" + } + }, + { + "name": "Check tcpflags2 (out of) options constraint", + "status": 400, + "return": 4246, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": ["INVALID"] + } + }, + { + "name": "Check tcpflags2 (out of) options constraint (CSV)", + "status": 400, + "return": 4246, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": "syn,INVALID" + } + }, + { + "name": "Check tcpflags1 (set) options constraint", + "status": 400, + "return": 4246, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": ["syn"], + "tcpflags1": ["INVALID"] + } + }, + { + "name": "Check tcpflags1 (set) options constraint (CSV)", + "status": 400, + "return": 4246, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": ["syn"], + "tcpflags1": ["INVALID"] + } + }, + { + "name": "Check tcpflags1 (set) must be in tcpflags2 (out of) constraint", + "status": 400, + "return": 4247, + "payload": { + "type": "pass", + "interface": "wan", + "ipprotocol": "inet", + "protocol": "tcp/udp", + "src": "any", + "dst": "any", + "srcport": "any", + "dstport": "any", + "tcpflags2": ["syn"], + "tcpflags1": ["fin"] + } + } ] delete_tests = [ {"name": "Delete firewall rule", "payload": {}}, # Tracker ID gets populated by post_post() method