From 92156e3813226458fbbfb0ced3612c33f0820d80 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Thu, 24 Dec 2020 15:15:31 -0700 Subject: [PATCH 1/4] Fixed bug that reinitialized the NAT configuration unexpectedly, allow firewall rules to be created with port aliases, restructured alias endpoints and improved input validation --- .../etc/inc/api/framework/APIResponse.inc | 10 +- .../files/etc/inc/api/framework/APITools.inc | 2 - .../inc/api/models/APIFirewallAliasCreate.inc | 219 +++++++++--------- .../inc/api/models/APIFirewallAliasDelete.inc | 57 ++--- .../models/APIFirewallAliasEntryDelete.inc | 15 +- .../inc/api/models/APIFirewallAliasUpdate.inc | 214 +++++++++++------ .../models/APIFirewallNATOneToOneCreate.inc | 22 +- .../inc/api/models/APIFirewallRuleCreate.inc | 4 +- .../inc/api/models/APIFirewallRuleUpdate.inc | 4 +- tests/test_api_v1_firewall_alias.py | 2 +- 10 files changed, 308 insertions(+), 241 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 c841cd1b3..5b8dc83aa 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc @@ -1212,7 +1212,7 @@ function get($id, $data=[], $all=false) { "status" => "bad request", "code" => 400, "return" => $id, - "message" => "Firewall alias name must be type string" + "message" => "Invalid firewall alias name" ], 4054 => [ "status" => "bad request", @@ -1254,7 +1254,7 @@ function get($id, $data=[], $all=false) { "status" => "bad request", "code" => 400, "return" => $id, - "message" => "Invalid firewall alias port" + "message" => "Invalid firewall alias port or port range" ], 4061 => [ "status" => "bad request", @@ -1532,6 +1532,12 @@ function get($id, $data=[], $all=false) { "return" => $id, "message" => "Alias details cannot contain more items than alias addresses" ], + 4107 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Alias type cannot be changed while in use" + ], //5000-5999 reserved for /users 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 906c6c651..8a4e12956 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc @@ -593,8 +593,6 @@ function alias_in_use($alias_name) { alias_find_references(array('nat', 'rule'), array('target'), $alias_name, $is_alias_referenced, $referenced_by); alias_find_references(array('nat', 'rule'), array('local-port'), $alias_name, $is_alias_referenced, $referenced_by); // NAT 1:1 Rules - //alias_find_references(array('nat', 'onetoone'), array('external'), $alias_name, $is_alias_referenced, $referenced_by); - //alias_find_references(array('nat', 'onetoone'), array('source', 'address'), $alias_name, $is_alias_referenced, $referenced_by); alias_find_references(array('nat', 'onetoone'), array('destination', 'address'), $alias_name, $is_alias_referenced, $referenced_by); // NAT Outbound Rules alias_find_references(array('nat', 'outbound', 'rule'), array('source', 'network'), $alias_name, $is_alias_referenced, $referenced_by); diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasCreate.inc index 70bfa72bc..4c1dd620f 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasCreate.inc @@ -32,147 +32,140 @@ class APIFirewallAliasCreate extends APIModel { send_event("filter reload"); // Ensure our firewall filter is reloaded return APIResponse\get(0, $this->validated_data); } - - public function validate_payload() { - $allowed_alias_types = array("host", "network", "port"); // Array of allowed alias types + private function __validate_name() { + # Require clients to specify a valid name for the alias if (isset($this->initial_data['name'])) { - $name = $this->initial_data['name']; - $name = APITools\sanitize_str($name); + # Ensure an alias with this name does not already exist + if (!is_alias($this->initial_data['name'])) { + # Ensure the requested name is valid and not reserved by the system + if (is_validaliasname($this->initial_data["name"])) { + $this->validated_data["name"] = $this->initial_data['name']; + } else { + $this->errors[] = APIResponse\get(4053); + } + } else { + $this->errors[] = APIResponse\get(4056); + } } else { $this->errors[] = APIResponse\get(4050); } + } + + private function __validate_type() { + # Require clients to specify a valid type for this alias if (isset($this->initial_data['type'])) { - $type = $this->initial_data['type']; - $type = trim($type); + # Require this type to be supported + if (in_array($this->initial_data['type'], ["host", "network", "port"])) { + $this->validated_data["type"] = $this->initial_data["type"]; + } else { + $this->errors[] = APIResponse\get(4057); + } } else { $this->errors[] = APIResponse\get(4061); } + } + + private function __validate_address() { if (isset($this->initial_data['address'])) { - $address = $this->initial_data['address']; - // Convert string to array - if (!is_array($address)) { - $address = array($address); + # Convert value to array if it is not already + if (!is_array($this->initial_data['address'])) { + $this->initial_data['address'] = [$this->initial_data['address']]; } - } else { - $this->errors[] = APIResponse\get(4052); - } - if (isset($this->initial_data['descr'])) { - $descr = $this->initial_data['descr']; - } - if (isset($this->initial_data['detail'])) { - $detail = $this->initial_data['detail']; - // Convert string to array - if (!is_array($detail)) { - $detail = array($detail); - } - } - // Check that our input is valid - if (!is_string($name)) { - $this->errors[] = APIResponse\get(4053); - } elseif (!is_string($type)) { - $this->errors[] = APIResponse\get(4062); - } elseif (!in_array($type, $allowed_alias_types)) { - $this->errors[] = APIResponse\get(4057); - } elseif (isset($descr) and !is_string($descr)) { - $this->errors[] = APIResponse\get(4063); - } elseif (!is_array($address)) { - $this->errors[] = APIResponse\get(4054); - } elseif (isset($detail) and !is_array($detail)) { - $this->errors[] = APIResponse\get(4063); - } - if (!isset($type_err)) { - // Loop through our arrays and ensure the values are valid - $a_count = 0; // Define a loop counter - foreach ($address as $ae) { - // Conditions for alias type 'port' - if ($type === "port") { - // Check that our value is numeric - if (is_numeric($ae)) { - if (1 <= intval($ae) and intval($ae) <= 65535) { - $address[$a_count] = strval($ae); - } else { - $this->errors[] = APIResponse\get(4065); - - } + # Loop through each address and ensure it is valid for our specified type + foreach($this->initial_data['address'] as $address) { + # Validation for host type aliases + if ($this->validated_data["type"] === "host") { + # Require this address to be a valid IPv4, IPv6, or FQDN + if (is_ipaddrv4($address) or is_ipaddrv6($address) or is_fqdn($address)) { + $this->validated_data["address"][] = $address; } else { - $this->errors[] = APIResponse\get(4066); - + $this->errors[] = APIResponse\get(4058); } } - // Conditionals for alias type 'network' - if ($type === "network") { - // Check that values are strings - if (is_string($ae)) { - // Check that string is a network CIDR - if (strpos($ae, "/")) { - $net_ip = explode("/", $ae)[0]; // Save our network IP - $bit_mask = explode("/", $ae)[1]; // Save our subnet bit mask - // Check if our IP is IPv4 - if (is_ipaddrv4($net_ip)) { - $max_bits = 32; // Assign our maximum IPv4 bitmask - } elseif (is_ipaddrv6($net_ip)) { - $max_bits = 128; // Assign our maximum IPv4 bitmask - } else { - $this->errors[] = APIResponse\get(4067); - - } - // Check if our bitmask is numeric and in range - if (is_numeric($bit_mask)) { - if (1 <= intval($bit_mask) and intval($bit_mask) <= $max_bits) { - continue; - } else { - $this->errors[] = APIResponse\get(4068); - } - } else { - $this->errors[] = APIResponse\get(4069); - } - } else { - $this->errors[] = APIResponse\get(4069); - - } + # Validation for network type aliases + elseif ($this->validated_data["type"] === "network") { + # Require this address to be a valid IPv4 subnet, IPv6 subnet, or FQDN + if (is_subnetv4($address) or is_subnetv6($address) or is_fqdn($address)) { + $this->validated_data["address"][] = $address; } else { - $this->errors[] = APIResponse\get(4070); - + $this->errors[] = APIResponse\get(4059); } } - // Conditions for alias type 'host' - if ($type === "host") { - // Check that values are strings - if (is_string($ae)) { - $address[$a_count] = APITools\sanitize_str($ae); - } else { - $this->errors[] = APIResponse\get(4070); + # Validation for port type aliases + elseif ($this->validated_data["type"] === "port") { + # Convert integers to string expected by is_port_or_range() function + $address = (is_int($address)) ? strval($address) : $address; + # Replace hyphen with colon + $address = str_replace("-", ":", $address); + + # Require this address to be a valid port or port range + if (is_port_or_range($address)) { + $this->validated_data["address"][] = $address; + } else { + $this->errors[] = APIResponse\get(4060); } } - // Increase our counter - $a_count++; + } - // Check each of our alias details - foreach ($detail as $de) { - if (!is_string($de)) { - $this->errors[] = APIResponse\get(4071); - } + # Convert our array to a space separated string as expected by the XML config + $this->validated_data["address"] = implode(" ", $this->validated_data["address"]); + } else { + $this->errors[] = APIResponse\get(4052); + } + } + + private function __validate_descr() { + # Optionally allow clients to specify an alias description + if (isset($this->initial_data['descr'])) { + $this->validated_data["descr"] = $this->initial_data['descr']; + } else { + $this->validated_data["descr"] = ""; + } + } + + private function __validate_detail() { + # Convert single values to an array + if (!is_array($this->initial_data["detail"])) { + $this->initial_data["detail"] = [$this->initial_data["detail"]]; + } + + # If we have less detail values than address values, add some defaults + while(true) { + # Check if we have the correct number of detail values + if (count($this->initial_data["detail"]) < count($this->initial_data["address"])) { + $this->initial_data["detail"][] = "Entry added " . date('r'); + } else { + break; } } - // Check our existing aliases - if (is_array($this->config["aliases"])) { - $c_count = 0; - // Loop through each alias and see if alias already exists - foreach ($this->config["aliases"]["alias"] as $ce) { - if ($ce["name"] === $name) { - $this->errors[] = APIResponse\get(4056); + # Ensure the number of detail values is less than or equal to the number of address values + if (count($this->initial_data["detail"]) <= count($this->initial_data["address"])) { + # Loop through each alias detail and ensure it is a string + foreach ($this->initial_data["detail"] as $detail) { + if (is_string($detail)) { + $this->validated_data["detail"][] = $detail; + } else { + $this->errors[] = APIResponse\get(4071); } } + + # Convert our array to pfSense's expected XML string format + $this->validated_data["detail"] = implode("||", $this->validated_data["detail"]); + } else { + $this->errors[] = APIResponse\get(4106); } - $this->validated_data["name"] = $name; // Save our alias name - $this->validated_data["type"] = $type; // Save our type - $this->validated_data["descr"] = $descr; // Save our description - $this->validated_data["address"] = implode(" ", $address); // Join array in to space seperated string - $this->validated_data["detail"] = implode("||", $detail); // Join array in to || seperated string + } + + public function validate_payload() { + $this->__validate_name(); + $this->__validate_type(); + $this->__validate_descr(); + $this->__validate_address(); + $this->__validate_detail(); } } \ No newline at end of file diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasDelete.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasDelete.inc index 601af7e4c..6a3f9fa55 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasDelete.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasDelete.inc @@ -25,45 +25,36 @@ class APIFirewallAliasDelete extends APIModel { } public function action() { - $del_conf = $this->config["aliases"]["alias"][$this->id]; // Capture alias config before deleting - unset($this->config["aliases"]["alias"][$this->id]); // Remove this alias from our configuration - $this->config["aliases"]["alias"] = array_values($this->config["aliases"]["alias"]); // Reindex array - $this->write_config(); // Apply our configuration change - send_event("filter reload"); // Ensure our firewall filter is reloaded + $del_conf = $this->config["aliases"]["alias"][$this->id]; + unset($this->config["aliases"]["alias"][$this->id]); + $this->config["aliases"]["alias"] = array_values($this->config["aliases"]["alias"]); + $this->write_config(); + send_event("filter reload"); return APIResponse\get(0, $del_conf); } - - public function validate_payload() { - - if (isset($this->initial_data['id'])) { - $name = $this->initial_data['id']; - $name = APITools\sanitize_str($name); - // Check that alias is not in use in our configuration - if (!APITools\alias_in_use($name)) { - // Loop through our current config and find the index ID for our alias to delete - $c_count = 0; // Init loop counter - foreach ($this->config["aliases"]["alias"] as $ce) { - // Check if this entry matches our requested value - if ($ce["name"] === $name) { - $del_index = $c_count; - break; - } - $c_count++; + private function __validate_id() { + # Require clients to pass in an ID field containing the alias name or index value + if (isset($this->initial_data["id"])) { + # Loop through each alias and check for a match + foreach ($this->config["aliases"]["alias"] as $id=>$alias) { + # First check if the ID matches the index value or the alias name + if ($this->initial_data["id"] === $id or $this->initial_data["id"] === $alias["name"]) { + $this->id = $id; + $this->validated_data = $alias; + break; } - - if (is_numeric($del_index)) { - $this->id = $del_index; - } else { - $this->errors[] = APIResponse\get(4055); - } - - } else { - $this->errors[] = APIResponse\get(4051); + } + # If we did not find an ID in the loop, return a not found error + if (is_null($this->id)) { + $this->errors[] = APIResponse\get(4055); } } else { - $this->errors = APIResponse\get(4074); + $this->errors[] = APIResponse\get(4050); } - + } + + public function validate_payload() { + $this->__validate_id(); } } \ No newline at end of file diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasEntryDelete.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasEntryDelete.inc index 1910d98af..3f87f3975 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasEntryDelete.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasEntryDelete.inc @@ -34,17 +34,12 @@ class APIFirewallAliasEntryDelete extends APIModel { private function __validate_name() { # Require clients to pass in a name value to locate the alias to add addresses to if (isset($this->initial_data['name'])) { - # Ensure our alias name is a string - if (is_string($this->initial_data['name'])) { - # Ensure this alias exists - $this->id = $this->__get_alias_id($this->initial_data["name"]); - if (isset($this->id)) { - $this->validated_data = $this->config["aliases"]["alias"][$this->id]; - } else { - $this->errors[] = APIResponse\get(4055); - } + # Ensure this alias exists + $this->id = $this->__get_alias_id($this->initial_data["name"]); + if (isset($this->id)) { + $this->validated_data = $this->config["aliases"]["alias"][$this->id]; } else { - $this->errors[] = APIResponse\get(4053); + $this->errors[] = APIResponse\get(4055); } } else { $this->errors[] = APIResponse\get(4050); diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasUpdate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasUpdate.inc index b0917402a..701017f34 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasUpdate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasUpdate.inc @@ -17,11 +17,14 @@ require_once("api/framework/APIModel.inc"); require_once("api/framework/APIResponse.inc"); class APIFirewallAliasUpdate extends APIModel { + private $type_changed; + # Create our method constructor public function __construct() { parent::__construct(); $this->privileges = ["page-all", "page-firewall-aliases-edit"]; $this->change_note = "Modified firewall alias address via API"; + $this->type_changed = false; } public function action() { @@ -30,99 +33,170 @@ class APIFirewallAliasUpdate extends APIModel { send_event("filter reload"); // Ensure our firewall filter is reloaded return APIResponse\get(0, $this->config["aliases"]["alias"][$this->id]); } - - public function validate_payload() { - $allowed_alias_types = array("host", "network", "port"); // Array of allowed alias types - if (isset($this->initial_data['id'])) { - $name = $this->initial_data['id']; - $name = APITools\sanitize_str($name); + + private function __validate_id() { + # Require clients to pass in an ID field containing the alias name or index value + if (isset($this->initial_data["id"])) { + # Loop through each alias and check for a match + foreach ($this->config["aliases"]["alias"] as $id=>$alias) { + # First check if the ID matches the index value or the alias name + if ($this->initial_data["id"] === $id or $this->initial_data["id"] === $alias["name"]) { + $this->id = $id; + $this->validated_data = $alias; + break; + } + } + # If we did not find an ID in the loop, return a not found error + if (is_null($this->id)) { + $this->errors[] = APIResponse\get(4055); + } } else { - $this->errors[] = APIResponse\get(4074); + $this->errors[] = APIResponse\get(4050); } + } + + private function __validate_name() { + # Optionally allow clients to update the alias name if (isset($this->initial_data['name'])) { - $new_name = $this->initial_data['name']; - $new_name = APITools\sanitize_str($new_name); + # Ensure an alias with this name does not already exist. Allow if the stored name matches the requested. + if (!is_alias($this->initial_data['name']) or $this->validated_data["name"] === $this->initial_data['name']) { + # Ensure the requested name is valid and not reserved by the system + if (is_validaliasname($this->initial_data["name"])) { + $this->validated_data["name"] = $this->initial_data['name']; + } else { + $this->errors[] = APIResponse\get(4053); + } + } else { + $this->errors[] = APIResponse\get(4056); + } } + } + + private function __validate_type() { + # Optionally allow clients to update the alias type if (isset($this->initial_data['type'])) { - $type = $this->initial_data['type']; - $type = trim($type); - } else { - $type = alias_get_type($name); - } - if (isset($this->initial_data['address'])) { - $address = $this->initial_data['address']; - // Convert string to array - if (!is_array($address)) { - $address = array($address); + # Require alias to not be in use to change the type + if (!APITools\alias_in_use($this->validated_data["name"])) { + # Require this type to be supported + if (in_array($this->initial_data['type'], ["host", "network", "port"])) { + $this->type_changed = true; + $this->validated_data["type"] = $this->initial_data["type"]; + } else { + $this->errors[] = APIResponse\get(4057); + } + } elseif ($this->initial_data["type"] !== $this->validated_data["type"]) { + $this->errors[] = APIResponse\get(4107); } } - if (isset($this->initial_data['descr'])) { - $descr = strval($this->initial_data['descr']); - $descr = str_replace(PHP_EOL, "", $descr); - } - if (isset($this->initial_data['detail'])) { - $detail = $this->initial_data['detail']; - // Convert string to array - if (!is_array($detail)) { - $detail = array($detail); + } + + private function __validate_address() { + if (isset($this->initial_data['address']) or $this->type_changed) { + # Revalidate the stored values if the type was changed but no new address values were requested + if ($this->type_changed and !isset($this->initial_data['address'])) { + $this->initial_data["address"] = explode(" ", $this->validated_data["address"]); } - } - // INPUT VALIDATION - if (!is_alias($name)) { - $this->errors[] = APIResponse\get(4055); - } elseif (isset($new_name) and is_alias($new_name)) { - $this->errors[] = APIResponse\get(4056); - } elseif (isset($type) and !in_array($type, $allowed_alias_types)) { - $this->errors[] = APIResponse\get(4057); - } - if (isset($address)) { - foreach ($address as $nae) { - if ($type === "host") { - if (!is_ipaddr($nae) and !is_hostname($nae)) { + # Convert value to array if it is not already + if (!is_array($this->initial_data['address'])) { + $this->initial_data['address'] = [$this->initial_data['address']]; + } + # Revert stored address to empty array + $this->validated_data["address"] = []; + # Loop through each address and ensure it is valid for our specified type + foreach($this->initial_data['address'] as $address) { + # Validation for host type aliases + if ($this->validated_data["type"] === "host") { + # Require this address to be a valid IPv4, IPv6, or FQDN + if (is_ipaddrv4($address) or is_ipaddrv6($address) or is_fqdn($address)) { + $this->validated_data["address"][] = $address; + } else { $this->errors[] = APIResponse\get(4058); - break; } } - if ($type === "network") { - if (!is_subnet($nae) and !is_hostname($nae)) { + # Validation for network type aliases + elseif ($this->validated_data["type"] === "network") { + # Require this address to be a valid IPv4 subnet, IPv6 subnet, or FQDN + if (is_subnetv4($address) or is_subnetv6($address) or is_fqdn($address)) { + $this->validated_data["address"][] = $address; + } else { $this->errors[] = APIResponse\get(4059); - break; } } - if ($type === "port") { - if (!is_port_or_range($nae)) { + # Validation for port type aliases + elseif ($this->validated_data["type"] === "port") { + # Convert integers to string expected by is_port_or_range() function + $address = (is_int($address)) ? strval($address) : $address; + + # Replace hyphen with colon + $address = str_replace("-", ":", $address); + + # Require this address to be a valid port or port range + if (is_port_or_range($address)) { + $this->validated_data["address"][] = $address; + } else { $this->errors[] = APIResponse\get(4060); - break; } } } + # Convert our array to a space separated string as expected by the XML config + $this->validated_data["address"] = implode(" ", $this->validated_data["address"]); + } else { + $this->errors[] = APIResponse\get(4052); } - // Save our alias ID - $c_count = 0; - foreach ($this->config["aliases"]["alias"] as $ca) { - if ($name === $ca["name"]) { - $this->id = $c_count; - break; - } - $c_count++; - } - // Make our alias change - $this->validated_data = $this->config["aliases"]["alias"][$this->id]; // Save our current alias entry - if (isset($new_name)) { - $this->validated_data["name"] = $new_name; - update_alias_name($new_name, $name); // Update our alias name + } + + private function __validate_descr() { + # Optionally allow clients to specify an alias description + if (isset($this->initial_data['descr'])) { + $this->validated_data["descr"] = $this->initial_data['descr']; } - if (isset($type)) { - $this->validated_data["type"] = $type; + } + + private function __validate_detail() { + # Revalidate the existing details if no new details were requested + if (!isset($this->initial_data["detail"])) { + $this->initial_data["detail"] = explode("||", $this->validated_data["detail"]); } - if (isset($descr)) { - $this->validated_data["descr"] = $descr; + # Convert single values to an array + if (!is_array($this->initial_data["detail"])) { + $this->initial_data["detail"] = [$this->initial_data["detail"]]; } - if (isset($address)) { - $this->validated_data["address"] = implode(" ", $address); + # Revert stored detail to empty array + $this->validated_data["detail"] = []; + # If we have less detail values than address values, add some defaults + while(true) { + # Check if we have the correct number of detail values + if (count($this->initial_data["detail"]) < count($this->initial_data["address"])) { + $this->initial_data["detail"][] = "Entry added " . date('r'); + } else { + break; + } } - if (isset($detail)) { - $this->validated_data["detail"] = implode("||", $detail); + + # Ensure the number of detail values is less than or equal to the number of address values + if (count($this->initial_data["detail"]) <= count($this->initial_data["address"])) { + # Loop through each alias detail and ensure it is a string + foreach ($this->initial_data["detail"] as $detail) { + if (is_string($detail)) { + $this->validated_data["detail"][] = $detail; + } else { + $this->errors[] = APIResponse\get(4071); + } + } + + # Convert our array to pfSense's expected XML string format + $this->validated_data["detail"] = implode("||", $this->validated_data["detail"]); + } else { + $this->errors[] = APIResponse\get(4106); } } + + public function validate_payload() { + $this->__validate_id(); + $this->__validate_type(); // Type must be validated before name as it depends on the original name + $this->__validate_name(); + $this->__validate_descr(); + $this->__validate_address(); + $this->__validate_detail(); + } } \ No newline at end of file diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallNATOneToOneCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallNATOneToOneCreate.inc index d286d4829..4b20cc7f8 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallNATOneToOneCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallNATOneToOneCreate.inc @@ -29,13 +29,8 @@ class APIFirewallNATOneToOneCreate extends APIModel { } public function action() { - # Ensure our config is formatted for onetoone rules - if (!is_array($this->config["nat"]["onetoone"])) { - $this->config["nat"] = []; - $this->config["nat"]["onetoone"] = []; - } - # Add our new configuration + $this->__init_array(); $this->config["nat"]["onetoone"][$this->id] = $this->validated_data; APITools\sort_nat_rules($this->initial_data["top"], $this->id, "onetoone"); $this->write_config(); @@ -118,6 +113,8 @@ class APIFirewallNATOneToOneCreate extends APIModel { # Optionally allow client to specify a description for this entry if (isset($this->initial_data["descr"])) { $this->validated_data["descr"] = $this->initial_data["descr"]; + } else { + $this->validated_data["descr"] = ""; } } @@ -133,6 +130,19 @@ class APIFirewallNATOneToOneCreate extends APIModel { } } + private function __init_array() { + # Ensure our NAT configuration is initialized as an array + if (!is_array($this->config["nat"])) { + $this->config["nat"] = []; + } + + # Ensure our one-to-one NAT configuration is initialized as an array + if (!is_array($this->config["nat"]["onetoone"])) { + $this->config["nat"]["onetoone"] = []; + } + + } + public function validate_payload() { # Run each field validator method $this->__validate_interface(); 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 13b1db8b4..c2136fd99 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleCreate.inc @@ -149,7 +149,7 @@ class APIFirewallRuleCreate extends APIModel { if (in_array($this->validated_data["protocol"], ["tcp", "udp", "tcp/udp"])) { if (isset($this->initial_data['srcport'])) { $val = str_replace("-", ":", $this->initial_data['srcport']); - if (!is_port_or_range($val) and $val !== "any") { + if (!is_port_or_range_or_alias($val) and $val !== "any") { $this->errors[] = APIResponse\get(4048); } elseif ($val !== "any") { $this->validated_data["source"]["port"] = str_replace(":", "-", $val);; @@ -160,7 +160,7 @@ class APIFirewallRuleCreate extends APIModel { if (isset($this->initial_data['dstport'])) { $val = str_replace("-", ":", $this->initial_data['dstport']); - if (!is_port_or_range($val) and $val !== "any") { + if (!is_port_or_range_or_alias($val) and $val !== "any") { $this->errors[] = APIResponse\get(4049); } elseif ($val !== "any") { $this->validated_data["destination"]["port"] = str_replace(":", "-", $val);; 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 3bd1ada34..cba3c99cc 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleUpdate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleUpdate.inc @@ -161,7 +161,7 @@ class APIFirewallRuleUpdate extends APIModel { if ($requires_port) { if (isset($this->initial_data['srcport'])) { $val = str_replace("-", ":", $this->initial_data['srcport']); - if (!is_port_or_range($val) and $val !== "any") { + if (!is_port_or_range_or_alias($val) and $val !== "any") { $this->errors[] = APIResponse\get(4048); } elseif ($val !== "any") { $this->validated_data["source"]["port"] = str_replace(":", "-", $val);; @@ -172,7 +172,7 @@ class APIFirewallRuleUpdate extends APIModel { if (isset($this->initial_data['dstport'])) { $val = str_replace("-", ":", $this->initial_data['dstport']); - if (!is_port_or_range($val) and $val !== "any") { + if (!is_port_or_range_or_alias($val) and $val !== "any") { $this->errors[] = APIResponse\get(4048); } elseif ($val !== "any") { $this->validated_data["destination"]["port"] = str_replace(":", "-", $val);; diff --git a/tests/test_api_v1_firewall_alias.py b/tests/test_api_v1_firewall_alias.py index b5216948b..485677176 100644 --- a/tests/test_api_v1_firewall_alias.py +++ b/tests/test_api_v1_firewall_alias.py @@ -45,7 +45,7 @@ class APIUnitTestFirewallAlias(unit_test_framework.APIUnitTest): { "id": "RFC1918", "name": "UPDATED_RFC1918", - "type": "network", + "type": "port", "descr": "Updated Unit Test", "address": ["10.0.0.0/32", "172.16.0.0/32", "192.168.0.0/32"], "detail": ["New Class A", "New Class B", "New Class C"] From caf8530376102622de6d0ccd269d3209dbf3545b Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 26 Dec 2020 15:06:48 -0700 Subject: [PATCH 2/4] Prevent aliases from being deleted if they are in use --- .../files/etc/inc/api/framework/APIResponse.inc | 10 ++++++++-- .../etc/inc/api/models/APIFirewallAliasDelete.inc | 9 +++++++-- tests/test_api_v1_firewall_alias.py | 2 +- tests/test_api_v1_firewall_rule.py | 4 ++-- 4 files changed, 18 insertions(+), 7 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 5b8dc83aa..d42574d17 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc @@ -1530,13 +1530,19 @@ function get($id, $data=[], $all=false) { "status" => "bad request", "code" => 400, "return" => $id, - "message" => "Alias details cannot contain more items than alias addresses" + "message" => "Firewall alias details cannot contain more items than alias addresses" ], 4107 => [ "status" => "bad request", "code" => 400, "return" => $id, - "message" => "Alias type cannot be changed while in use" + "message" => "Firewall alias type cannot be changed while in use" + ], + 4108 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Firewall alias cannot be deleted while in use" ], //5000-5999 reserved for /users API calls diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasDelete.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasDelete.inc index 6a3f9fa55..ab34bbd0a 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasDelete.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasDelete.inc @@ -40,8 +40,13 @@ class APIFirewallAliasDelete extends APIModel { foreach ($this->config["aliases"]["alias"] as $id=>$alias) { # First check if the ID matches the index value or the alias name if ($this->initial_data["id"] === $id or $this->initial_data["id"] === $alias["name"]) { - $this->id = $id; - $this->validated_data = $alias; + # Only allow deletion if the firewall alias is not in use. + if (!APITools\alias_in_use($alias["name"])) { + $this->id = $id; + $this->validated_data = $alias; + } else { + $this->errors[] = APIResponse\get(4108); + } break; } } diff --git a/tests/test_api_v1_firewall_alias.py b/tests/test_api_v1_firewall_alias.py index 485677176..b5216948b 100644 --- a/tests/test_api_v1_firewall_alias.py +++ b/tests/test_api_v1_firewall_alias.py @@ -45,7 +45,7 @@ class APIUnitTestFirewallAlias(unit_test_framework.APIUnitTest): { "id": "RFC1918", "name": "UPDATED_RFC1918", - "type": "port", + "type": "network", "descr": "Updated Unit Test", "address": ["10.0.0.0/32", "172.16.0.0/32", "192.168.0.0/32"], "detail": ["New Class A", "New Class B", "New Class C"] diff --git a/tests/test_api_v1_firewall_rule.py b/tests/test_api_v1_firewall_rule.py index 40004a973..3be078a88 100644 --- a/tests/test_api_v1_firewall_rule.py +++ b/tests/test_api_v1_firewall_rule.py @@ -38,9 +38,9 @@ class APIUnitTestFirewallRule(unit_test_framework.APIUnitTest): "ipprotocol": "inet", "protocol": "tcp/udp", "src": "172.16.77.125", - "srcport": "any", + "srcport": "HTTP", "dst": "127.0.0.1", - "dstport": "8443", + "dstport": "HTTP", "descr": "Updated Unit test", "top": True } From e23fd90ffbb2a50536e7a357efe670b920420b1d Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 26 Dec 2020 15:16:37 -0700 Subject: [PATCH 3/4] Fixed issue that prevented alias references from being updated when updating alias names --- .../inc/api/models/APIFirewallAliasUpdate.inc | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasUpdate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasUpdate.inc index 701017f34..a291ed8d1 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasUpdate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallAliasUpdate.inc @@ -18,6 +18,7 @@ require_once("api/framework/APIResponse.inc"); class APIFirewallAliasUpdate extends APIModel { private $type_changed; + private $original_name; # Create our method constructor public function __construct() { @@ -28,9 +29,10 @@ class APIFirewallAliasUpdate extends APIModel { } public function action() { - $this->config["aliases"]["alias"][$this->id] = $this->validated_data; // Write to our master config - $this->write_config(); // Apply our configuration change - send_event("filter reload"); // Ensure our firewall filter is reloaded + $this->config["aliases"]["alias"][$this->id] = $this->validated_data; + $this->__rename_alias_references__(); + $this->write_config(); + send_event("filter reload"); return APIResponse\get(0, $this->config["aliases"]["alias"][$this->id]); } @@ -43,6 +45,7 @@ class APIFirewallAliasUpdate extends APIModel { if ($this->initial_data["id"] === $id or $this->initial_data["id"] === $alias["name"]) { $this->id = $id; $this->validated_data = $alias; + $this->original_name = $alias["name"]; break; } } @@ -76,7 +79,7 @@ class APIFirewallAliasUpdate extends APIModel { # Optionally allow clients to update the alias type if (isset($this->initial_data['type'])) { # Require alias to not be in use to change the type - if (!APITools\alias_in_use($this->validated_data["name"])) { + if (!APITools\alias_in_use($this->original_name)) { # Require this type to be supported if (in_array($this->initial_data['type'], ["host", "network", "port"])) { $this->type_changed = true; @@ -193,10 +196,17 @@ class APIFirewallAliasUpdate extends APIModel { public function validate_payload() { $this->__validate_id(); - $this->__validate_type(); // Type must be validated before name as it depends on the original name $this->__validate_name(); + $this->__validate_type(); $this->__validate_descr(); $this->__validate_address(); $this->__validate_detail(); } + + private function __rename_alias_references__() { + # Only update alias references if our name was changed + if ($this->original_name !== $this->validated_data["name"]) { + update_alias_name($this->validated_data["name"], $this->original_name); + } + } } \ No newline at end of file From 5c3e70946d948d73383b7f91cfa2d9de22d395c8 Mon Sep 17 00:00:00 2001 From: Jared Hendrickson Date: Sat, 26 Dec 2020 16:11:31 -0700 Subject: [PATCH 4/4] Updated documentation --- README.md | 18 +++++------------- docs/documentation.json | 17 +++++++++-------- .../usr/local/www/api/documentation/index.php | 8 ++++---- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index e72053d27..fd55ed370 100644 --- a/README.md +++ b/README.md @@ -886,14 +886,6 @@ URL: https://{{$hostname}}/api/v1/firewall/alias -***Query params:*** - -| Key | Value | Description | -| --- | ------|-------------| -| id | string | Name of alias to delete. This alias must NOT be in use elsewhere in configuration | - - - ***Body:*** ```js @@ -1741,8 +1733,8 @@ URL: https://{{$hostname}}/api/v1/firewall/rule | icmptype | string or array | Set the ICMP subtype of the firewall rule. Multiple values may be passed in as array, single values may be passed as string. _Only available when `protocol` is set to `icmp`. If `icmptype` is not specified all subtypes are assumed_ | | src | string | Set the source address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interfance name, or the pfSense ID. To use only interface address, add `ip` to the end of the interface name otherwise the entire interface's subnet is implied. To negate the context of the source address, you may prepend the address with `!` | | dst | string | Set the destination address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interface name, or the pfSense ID. To only use interface address, add `ip` to the end of the interface name otherwise the entire interface's subnet is implied. To negate the context of the source address, you may prepend the address with `!` | -| srcport | string or integer | Set the TCP and/or UDP source port of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp` | -| dstport | string or integer | Set the TCP and/or UDP destination port of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp` | +| srcport | string or integer | Set the TCP and/or UDP source port or port alias of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp` | +| dstport | string or integer | Set the TCP and/or UDP destination port or port alias of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp` | | gateway | string | Set the routing gateway traffic will take upon match (optional) | | disabled | boolean | Disable the rule upon creation (optional) | | descr | string | Set a description for the rule (optional) | @@ -1868,9 +1860,9 @@ URL: https://{{$hostname}}/api/v1/firewall/rule | icmptype | string or array | Update the ICMP subtype of the firewall rule. Multiple values may be passed in as array, single values may be passed as string. _Only available when `protocol` is set to `icmp`. If `icmptype` is not specified all subtypes are assumed_ (optional) | | src | string | Update the source address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interfance name, or the pfSense ID. To use only interface address, add `ip` to the end of the interface name otherwise the entire interface's subnet is implied. To negate the context of the source address, you may prepend the address with `!` (optional) | | dst | string | Update the destination address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interface name, or the pfSense ID. To only use interface address, add `ip` to the end of the interface name otherwise the entire interface's subnet is implied. To negate the context of the source address, you may prepend the address with `!` (optional) | -| srcport | string or integer | Update the TCP and/or UDP source port of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp` (optional) | -| dstport | string or integer | Update the TCP and/or UDP destination port of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp` | -| gateway | string | UPdate the routing gateway traffic will take upon match (optional) | +| srcport | string or integer | Update the TCP and/or UDP source port or port alias of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp` (optional) | +| dstport | string or integer | Update the TCP and/or UDP destination port or port alias of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp` | +| gateway | string | Update the routing gateway traffic will take upon match (optional) | | disabled | boolean | Disable the rule upon modification (optional) | | descr | string | Update the description of the rule (optional) | | log | boolean | Enable rule matched logging (optional) | diff --git a/docs/documentation.json b/docs/documentation.json index e6c611c82..875069f9d 100644 --- a/docs/documentation.json +++ b/docs/documentation.json @@ -1980,7 +1980,7 @@ { "key": "type", "value": "string", - "description": "Change type of alias (optional)", + "description": "Change type of alias. Alias type can only be changed when the targetted alias is not in use (optional)", "disabled": true }, { @@ -2022,7 +2022,7 @@ } }, "url": { - "raw": "https://{{$hostname}}/api/v1/firewall/alias?id=string", + "raw": "https://{{$hostname}}/api/v1/firewall/alias", "protocol": "https", "host": [ "{{$hostname}}" @@ -2037,7 +2037,8 @@ { "key": "id", "value": "string", - "description": "Name of alias to delete. This alias must NOT be in use elsewhere in configuration" + "description": "Name or ID of alias to delete. This alias cannot be in use elsewhere in configuration", + "disabled": true } ] }, @@ -2174,12 +2175,12 @@ { "key": "srcport", "value": "string or integer", - "description": "Set the TCP and/or UDP source port of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp`" + "description": "Set the TCP and/or UDP source port or port alias of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp`" }, { "key": "dstport", "value": "string or integer", - "description": "Set the TCP and/or UDP destination port of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp`" + "description": "Set the TCP and/or UDP destination port or port alias of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp`" }, { "key": "gateway", @@ -2287,17 +2288,17 @@ { "key": "srcport", "value": "string or integer", - "description": "Update the TCP and/or UDP source port of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp` (optional)" + "description": "Update the TCP and/or UDP source port or port alias of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp` (optional)" }, { "key": "dstport", "value": "string or integer", - "description": "Update the TCP and/or UDP destination port of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp`" + "description": "Update the TCP and/or UDP destination port or port alias of the firewall rule. This is only necessary if you have specified the `protocol` to `tcp`, `udp`, `tcp/udp`" }, { "key": "gateway", "value": "string", - "description": "UPdate the routing gateway traffic will take upon match (optional)" + "description": "Update the routing gateway traffic will take upon match (optional)" }, { "key": "disabled", diff --git a/pfSense-pkg-API/files/usr/local/www/api/documentation/index.php b/pfSense-pkg-API/files/usr/local/www/api/documentation/index.php index 8a51431e8..5cc295e1f 100644 --- a/pfSense-pkg-API/files/usr/local/www/api/documentation/index.php +++ b/pfSense-pkg-API/files/usr/local/www/api/documentation/index.php @@ -672,7 +672,7 @@

Delete Firewall Aliases  |  DELETE   -https:///api/v1/firewall/alias

Description

Delete an existing alias and reload filter.

Requires at least one of the following privileges: [page-all, page-firewall-aliases-edit]

Query
KeyValueDescription
idstring

Name of alias to delete. This alias must NOT be in use elsewhere in configuration

Body
{ +https:///api/v1/firewall/alias
Description

Delete an existing alias and reload filter.

Requires at least one of the following privileges: [page-all, page-firewall-aliases-edit]

Body
{ "id": "RFC1918" }
Description

Add a new firewall rule.

Requires at least one of the following privileges: [page-all, page-firewall-rules-edit]

Query
KeyValueDescription
typestring

Set a firewall rule type (pass, block, reject)

interfacestring

Set which interface the rule will apply to. You may specify either the interface’s descriptive name, the pfSense ID (wan, lan, optx), or the physical interface id (e.g. igb0). Floating rules are not supported.

ipprotocolstring

Set which IP protocol(s) the rule will apply to (inet, inet6, inet46)

protocolstring

Set which transfer protocol the rule will apply to. If tcp, udp, tcp/udp, you must define a source and destination port

icmptypestring or array

Set the ICMP subtype of the firewall rule. Multiple values may be passed in as array, single values may be passed as string. Only available when protocol is set to icmp. If icmptype is not specified all subtypes are assumed

srcstring

Set the source address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interfance name, or the pfSense ID. To use only interface address, add ip to the end of the interface name otherwise the entire interface’s subnet is implied. To negate the context of the source address, you may prepend the address with !

dststring

Set the destination address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interface name, or the pfSense ID. To only use interface address, add ip to the end of the interface name otherwise the entire interface’s subnet is implied. To negate the context of the source address, you may prepend the address with !

srcportstring or integer

Set the TCP and/or UDP source port of the firewall rule. This is only necessary if you have specified the protocol to tcp, udp, tcp/udp

dstportstring or integer

Set the TCP and/or UDP destination port of the firewall rule. This is only necessary if you have specified the protocol to tcp, udp, tcp/udp

gatewaystring

Set the routing gateway traffic will take upon match (optional)

disabledboolean

Disable the rule upon creation (optional)

descrstring

Set a description for the rule (optional)

logboolean

Enabling rule matche logging (optional)

topboolean

Add firewall rule to top of access control list (optional)

applyboolean

Specify whether or not you would like this rule to be applied immediately, or simply written to the configuration to be applied later. Typically, if you are creating multiple rules at once it Is best to set this to false and apply the changes afterwards using the /api/v1/firewall/apply endpoint. Otherwise, If you are only creating a single rule, you may set this true to apply it immediately. Defaults to false. (optional)

Body
{ +https:///api/v1/firewall/rule
Description

Add a new firewall rule.

Requires at least one of the following privileges: [page-all, page-firewall-rules-edit]

Query
KeyValueDescription
typestring

Set a firewall rule type (pass, block, reject)

interfacestring

Set which interface the rule will apply to. You may specify either the interface’s descriptive name, the pfSense ID (wan, lan, optx), or the physical interface id (e.g. igb0). Floating rules are not supported.

ipprotocolstring

Set which IP protocol(s) the rule will apply to (inet, inet6, inet46)

protocolstring

Set which transfer protocol the rule will apply to. If tcp, udp, tcp/udp, you must define a source and destination port

icmptypestring or array

Set the ICMP subtype of the firewall rule. Multiple values may be passed in as array, single values may be passed as string. Only available when protocol is set to icmp. If icmptype is not specified all subtypes are assumed

srcstring

Set the source address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interfance name, or the pfSense ID. To use only interface address, add ip to the end of the interface name otherwise the entire interface’s subnet is implied. To negate the context of the source address, you may prepend the address with !

dststring

Set the destination address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interface name, or the pfSense ID. To only use interface address, add ip to the end of the interface name otherwise the entire interface’s subnet is implied. To negate the context of the source address, you may prepend the address with !

srcportstring or integer

Set the TCP and/or UDP source port or port alias of the firewall rule. This is only necessary if you have specified the protocol to tcp, udp, tcp/udp

dstportstring or integer

Set the TCP and/or UDP destination port or port alias of the firewall rule. This is only necessary if you have specified the protocol to tcp, udp, tcp/udp

gatewaystring

Set the routing gateway traffic will take upon match (optional)

disabledboolean

Disable the rule upon creation (optional)

descrstring

Set a description for the rule (optional)

logboolean

Enabling rule matche logging (optional)

topboolean

Add firewall rule to top of access control list (optional)

applyboolean

Specify whether or not you would like this rule to be applied immediately, or simply written to the configuration to be applied later. Typically, if you are creating multiple rules at once it Is best to set this to false and apply the changes afterwards using the /api/v1/firewall/apply endpoint. Otherwise, If you are only creating a single rule, you may set this true to apply it immediately. Defaults to false. (optional)

Body
{ "type": "block", "interface": "wan", "ipprotocol": "inet", @@ -900,7 +900,7 @@

Update Firewall Rules  |  PUT   -https:///api/v1/firewall/rule

Description

Update an existing firewall rule.

Requires at least one of the following privileges: [page-all, page-firewall-rules-edit]

Query
KeyValueDescription
trackerstring or Integer

Specify the tracker ID of the rule to update

typestring

Update the firewall rule type (pass, block, reject) (optional)

interfacestring

Update the interface the rule will apply to. You may specify either the interface’s descriptive name, the pfSense ID (wan, lan, optx), or the physical interface id (e.g. igb0). Floating rules are not supported. (optional)

ipprotocolstring

Update which IP protocol(s) the rule will apply to (inet, inet6, inet46) (optional)

protocolstring

Update the transfer protocol the rule will apply to. If tcp, udp, tcp/udp, you must define a source and destination port. (optional)

icmptypestring or array

Update the ICMP subtype of the firewall rule. Multiple values may be passed in as array, single values may be passed as string. Only available when protocol is set to icmp. If icmptype is not specified all subtypes are assumed (optional)

srcstring

Update the source address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interfance name, or the pfSense ID. To use only interface address, add ip to the end of the interface name otherwise the entire interface’s subnet is implied. To negate the context of the source address, you may prepend the address with ! (optional)

dststring

Update the destination address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interface name, or the pfSense ID. To only use interface address, add ip to the end of the interface name otherwise the entire interface’s subnet is implied. To negate the context of the source address, you may prepend the address with ! (optional)

srcportstring or integer

Update the TCP and/or UDP source port of the firewall rule. This is only necessary if you have specified the protocol to tcp, udp, tcp/udp (optional)

dstportstring or integer

Update the TCP and/or UDP destination port of the firewall rule. This is only necessary if you have specified the protocol to tcp, udp, tcp/udp

gatewaystring

UPdate the routing gateway traffic will take upon match (optional)

disabledboolean

Disable the rule upon modification (optional)

descrstring

Update the description of the rule (optional)

logboolean

Enable rule matched logging (optional)

topboolean

Move firewall rule to top of access control list (optional)

applyboolean

Specify whether or not you would like this rule update to be applied immediately, or simply written to the configuration to be applied later. Typically, if you are updating multiple rules at once it Is best to set this to false and apply the changes afterwards using the /api/v1/firewall/apply endpoint. Otherwise, If you are only updating a single rule, you may set this true to apply it immediately. Defaults to false. (optional)

Body
{ +https:///api/v1/firewall/rule
Description

Update an existing firewall rule.

Requires at least one of the following privileges: [page-all, page-firewall-rules-edit]

Query
KeyValueDescription
trackerstring or Integer

Specify the tracker ID of the rule to update

typestring

Update the firewall rule type (pass, block, reject) (optional)

interfacestring

Update the interface the rule will apply to. You may specify either the interface’s descriptive name, the pfSense ID (wan, lan, optx), or the physical interface id (e.g. igb0). Floating rules are not supported. (optional)

ipprotocolstring

Update which IP protocol(s) the rule will apply to (inet, inet6, inet46) (optional)

protocolstring

Update the transfer protocol the rule will apply to. If tcp, udp, tcp/udp, you must define a source and destination port. (optional)

icmptypestring or array

Update the ICMP subtype of the firewall rule. Multiple values may be passed in as array, single values may be passed as string. Only available when protocol is set to icmp. If icmptype is not specified all subtypes are assumed (optional)

srcstring

Update the source address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interfance name, or the pfSense ID. To use only interface address, add ip to the end of the interface name otherwise the entire interface’s subnet is implied. To negate the context of the source address, you may prepend the address with ! (optional)

dststring

Update the destination address of the firewall rule. This may be a single IP, network CIDR, alias name, or interface. When specifying an interface, you may use the physical interface ID, the descriptive interface name, or the pfSense ID. To only use interface address, add ip to the end of the interface name otherwise the entire interface’s subnet is implied. To negate the context of the source address, you may prepend the address with ! (optional)

srcportstring or integer

Update the TCP and/or UDP source port or port alias of the firewall rule. This is only necessary if you have specified the protocol to tcp, udp, tcp/udp (optional)

dstportstring or integer

Update the TCP and/or UDP destination port or port alias of the firewall rule. This is only necessary if you have specified the protocol to tcp, udp, tcp/udp

gatewaystring

Update the routing gateway traffic will take upon match (optional)

disabledboolean

Disable the rule upon modification (optional)

descrstring

Update the description of the rule (optional)

logboolean

Enable rule matched logging (optional)

topboolean

Move firewall rule to top of access control list (optional)

applyboolean

Specify whether or not you would like this rule update to be applied immediately, or simply written to the configuration to be applied later. Typically, if you are updating multiple rules at once it Is best to set this to false and apply the changes afterwards using the /api/v1/firewall/apply endpoint. Otherwise, If you are only updating a single rule, you may set this true to apply it immediately. Defaults to false. (optional)

Body
{ "tracker": 1600981687, "type": "pass", "interface": "wan", @@ -1705,7 +1705,7 @@ function IsJsonString(str){try{JSON.parse(str);}catch(e){return false;} return true;} String.prototype.replaceAll=function(replaceThis,withThis){var re=new RegExp(RegExp.quote(replaceThis),"g");return this.replace(re,withThis);};RegExp.quote=function(str){return str.replace(/([.?*+^$[\]\\(){}-])/g,"\\$1");};function syntaxHighlight(json){json=json.replace(/&/g,'&').replace(//g,'>');return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,function(match){var cls='number';if(/^"/.test(match)){if(/:$/.test(match)){cls='key';}else{cls='string';}}else if(/true|false/.test(match)){cls='boolean';}else if(/null/.test(match)){cls='null';} -return ''+match+'';});}

+return ''+match+'';});}