Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Firewall floating rule support #188

Merged
merged 8 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1934,7 +1934,7 @@ URL: https://{{$hostname}}/api/v1/firewall/rule
| Key | Type | Description |
| --- | ------|-------------|
| type | string | Set a firewall rule type (`pass`, `block`, `reject`) |
| interface | string | 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. |
| interface | string | 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). |
| ipprotocol | string | Set which IP protocol(s) the rule will apply to (`inet`, `inet6`, `inet46`) |
| protocol | string | Set which transfer protocol the rule will apply to. If `tcp`, `udp`, `tcp/udp`, you must define a source and destination port |
| 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_ |
Expand All @@ -1951,6 +1951,8 @@ URL: https://{{$hostname}}/api/v1/firewall/rule
| disabled | boolean | Disable the rule upon creation (optional) |
| descr | string | Set a description for the rule (optional) |
| log | boolean | Enabling rule matched logging (optional) |
| floating | boolean | Set firewall rule is floating rule (optional) |
| direction | string | Set the direction of firewall floating rule, This is only necessary when `floating` is set. Set a firewall rule direction (`in`, `out`, `any`), if not set default to (`any`) (optional) |
| top | boolean | Add firewall rule to top of access control list (optional) |
| apply | boolean | 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) |

Expand Down Expand Up @@ -2063,7 +2065,7 @@ URL: https://{{$hostname}}/api/v1/firewall/rule
| --- | ------|-------------|
| tracker | string or Integer | Specify the tracker ID of the rule to update |
| type | string | Update the firewall rule type (`pass`, `block`, `reject`) (optional) |
| interface | string | 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) |
| interface | string | 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). (optional) |
| ipprotocol | string | Update which IP protocol(s) the rule will apply to (`inet`, `inet6`, `inet46`) (optional) |
| protocol | string | Update the transfer protocol the rule will apply to. If `tcp`, `udp`, `tcp/udp`, you must define a source and destination port. (optional) |
| 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) |
Expand All @@ -2081,6 +2083,8 @@ URL: https://{{$hostname}}/api/v1/firewall/rule
| descr | string | Update the description of the rule (optional) |
| log | boolean | Enable rule matched logging (optional) |
| top | boolean | Move firewall rule to top of access control list (optional) |
| floating | boolean | Set firewall rule is floating rule (optional) |
| direction | string | Set the direction of firewall floating rule, This is only necessary when `floating` is set. Set a firewall rule direction (`in`, `out`, `any`), if not set default to (`any`) (optional) |
| apply | boolean | 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) |


Expand Down
6 changes: 6 additions & 0 deletions pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3440,6 +3440,12 @@ function get($id, $data=[], $all=false) {
"return" => $id,
"message" => "Only 1 dynamic gateway per eligible interface is allowed"
],
6030 => [
"status" => "bad request",
"code" => 400,
"return" => $id,
"message" => "Unknown API floating rule direction specified"
],

// 7000-7999 reserved for /diagnostics API calls
7000 => [
Expand Down
24 changes: 24 additions & 0 deletions pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleCreate.inc
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,28 @@ class APIFirewallRuleCreate extends APIModel {
}
}

private function __validate_floating() {
# Check for floating rule
if ($this->initial_data["floating"] === true) {
$this->validated_data["floating"] = "yes";
}
}

private function __validate_direction() {
# Validate our optional 'floating' payload value
if ($this->initial_data["floating"] === true) {
if (isset($this->initial_data["direction"])) {
if (in_array($this->initial_data["direction"], ["in", "out", "any"])) {
$this->validated_data["direction"] = $this->initial_data["direction"];
} else {
$this->errors[] = APIResponse\get(6030);
}
} else {
$this->validated_data["direction"] = "any";
}
}
}

public function validate_payload() {
$this->__validate_type();
$this->__validate_interface();
Expand All @@ -361,6 +383,8 @@ class APIFirewallRuleCreate extends APIModel {
$this->__validate_descr();
$this->__validate_log();
$this->__validate_top();
$this->__validate_floating();
$this->__validate_direction();

# 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
Expand Down
28 changes: 28 additions & 0 deletions pfSense-pkg-API/files/etc/inc/api/models/APIFirewallRuleUpdate.inc
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,32 @@ 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"])) {
if (isset($this->initial_data["direction"])) {
# Validate our optional 'direction' payload value
if (in_array($this->initial_data["direction"], ["in", "out", "any"])) {
$this->validated_data["direction"] = $this->initial_data["direction"];
} else {
$this->errors[] = APIResponse\get(6030);
}
} elseif (empty($this->validated_data["direction"])) {
$this->validated_data["direction"] = "any";
}
}
}

public function validate_payload() {
$this->__validate_tracker();
$this->__validate_type();
Expand All @@ -418,6 +444,8 @@ class APIFirewallRuleUpdate extends APIModel {
$this->__validate_descr();
$this->__validate_log();
$this->__validate_top();
$this->__validate_floating();
$this->__validate_direction();

# Update our 'updated' value
$this->validated_data["updated"] = [
Expand Down

Large diffs are not rendered by default.

73 changes: 70 additions & 3 deletions tests/test_api_v1_firewall_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,22 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest):
},
"resp_time": 3 # Accommodate the mandatory 1 second delay for firewall rule creations
},
{
"name": "Create floating firewall rule",
"payload": {
"type": "block",
"interface": "wan",
"ipprotocol": "inet",
"protocol": "tcp/udp",
"src": "172.16.77.121",
"srcport": "any",
"dst": "127.0.0.1",
"dstport": "443",
"descr": "Unit test",
"floating": True
},
"resp_time": 3 # Accommodate the mandatory 1 second delay for firewall rule creations
},
{
"name": "Test type requirement",
"status": 400,
Expand Down Expand Up @@ -482,6 +498,23 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest):
"ackqueue": "Test_Altq"
}
},
{
"name": "Test unknown floating direction",
"status": 400,
"return": 6030,
"payload": {
"type": "pass",
"interface": "wan",
"ipprotocol": "inet",
"protocol": "tcp",
"src": "any",
"dst": "any",
"srcport": "any",
"dstport": "any",
"floating": True,
"direction": "Test_Direction"
}
},
]
put_tests = [
{
Expand All @@ -503,6 +536,22 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest):
},
"resp_time": 3 # Allow a few seconds for the firewall filter to reload
},
{
"name": "Update floating firewall rule",
"payload": {
"type": "block",
"interface": "wan",
"ipprotocol": "inet",
"protocol": "tcp/udp",
"src": "172.16.77.121",
"srcport": "any",
"dst": "127.0.0.1",
"dstport": "443",
"descr": "Unit test",
"direction": "out"
},
"resp_time": 3 # Allow a few seconds for the firewall filter to reload
},
{
"name": "Test tracker requirement",
"status": 400,
Expand Down Expand Up @@ -644,9 +693,26 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest):
"sched": "INVALID"
}
},
{
"name": "Test unknown floating direction",
"status": 400,
"return": 6030,
"payload": {
"type": "pass",
"interface": "wan",
"ipprotocol": "inet",
"protocol": "tcp",
"src": "any",
"dst": "any",
"srcport": "any",
"dstport": "any",
"direction": "Test_Direction"
}
},
]
delete_tests = [
{"name": "Delete firewall rule", "payload": {}}, # Tracker ID gets populated by post_post() method
{"name": "Delete floating firewall rule", "payload": {}}, # Tracker ID gets populated by post_post() method
{
"name": "Delete traffic shaper queue used to test",
"uri": "/api/v1/firewall/traffic_shaper/queue",
Expand Down Expand Up @@ -682,15 +748,16 @@ class APIE2ETestFirewallRule(e2e_test_framework.APIE2ETest):

# Override our PRE/POST methods
def post_post(self):
# We create a firewall rule in the 7th test, ensure we have run at least 7 tests
if len(self.post_responses) == 7:
# We create a firewall rule in the 7th and 8th test, ensure we have run at least 8 tests
if len(self.post_responses) == 8:
# Assign the required tracker ID created in the POST request to the PUT and DELETE payloads
self.delete_tests[0]["payload"]["tracker"] = self.post_responses[6]["data"]["tracker"]

self.delete_tests[1]["payload"]["tracker"] = self.post_responses[7]["data"]["tracker"]
key = 0
for value in self.put_tests:
if "payload" in self.put_tests[key].keys():
self.put_tests[key]["payload"]["tracker"] = self.post_responses[6]["data"]["tracker"]
self.put_tests[key]["payload"]["tracker"] = self.post_responses[7]["data"]["tracker"]
key += 1

APIE2ETestFirewallRule()
24 changes: 22 additions & 2 deletions tools/templates/documentation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2579,7 +2579,7 @@
{
"key": "interface",
"value": "string",
"description": "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. "
"description": "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)."
},
{
"key": "ipprotocol",
Expand Down Expand Up @@ -2660,6 +2660,16 @@
"key": "log",
"value": "boolean",
"description": "Enabling rule matched logging (optional)"
},
{
"key": "floating",
"value": "boolean",
"description": "Set firewall rule is floating rule (optional)"
},
{
"key": "direction",
"value": "string",
"description": "Set the direction of firewall floating rule, This is only necessary when `floating` is set. Set a firewall rule direction (`in`, `out`, `any`), if not set default to (`any`) (optional)"
},
{
"key": "top",
Expand Down Expand Up @@ -2717,7 +2727,7 @@
{
"key": "interface",
"value": "string",
"description": "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)"
"description": "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). (optional)"
},
{
"key": "ipprotocol",
Expand Down Expand Up @@ -2804,6 +2814,16 @@
"value": "boolean",
"description": "Move firewall rule to top of access control list (optional)"
},
{
"key": "floating",
"value": "boolean",
"description": "Set firewall rule is floating rule (optional)"
},
{
"key": "direction",
"value": "string",
"description": "Set the direction of firewall floating rule, This is only necessary when `floating` is set. Set a firewall rule direction (`in`, `out`, `any`), if not set default to (`any`) (optional)"
},
{
"key": "apply",
"value": "boolean",
Expand Down