- reuse filter template, link endpoint to selected controller (filter/snat)
- push shared code to FilterBaseController
This commit is contained in:
Ad Schellevis
2020-03-30 19:00:01 +02:00
parent cbd04067d1
commit 13978ece64
10 changed files with 384 additions and 74 deletions
@@ -0,0 +1,99 @@
<?php
/*
* Copyright (C) 2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Firewall\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
/**
* Class FilterBaseController implements actions for various types
* @package OPNsense\Firewall\Api
*/
class FilterBaseController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'filter';
protected static $internalModelClass = 'OPNsense\Firewall\Filter';
public function applyAction($rollback_revision = null)
{
if ($this->request->isPost()) {
if ($rollback_revision != null) {
// background rollback timer
(new Backend())->configdpRun('pfplugin rollback_timer', [$rollback_revision], true);
}
return array("status" => (new Backend())->configdRun('filter reload'));
} else {
return array("status" => "error");
}
}
public function cancelRollbackAction($rollback_revision)
{
if ($this->request->isPost()) {
return array(
"status" => (new Backend())->configdpRun('pfplugin cancel_rollback', [$rollback_revision])
);
} else {
return array("status" => "error");
}
}
public function savepointAction()
{
if ($this->request->isPost()) {
// trigger a save, so we know revision->time matches our running config
Config::getInstance()->save();
return array(
"status" => "ok",
"retention" => (string)Config::getInstance()->backupCount(),
"revision" => (string)Config::getInstance()->object()->revision->time
);
} else {
return array("status" => "error");
}
}
public function revertAction($revision)
{
if ($this->request->isPost()) {
Config::getInstance()->lock();
$filename = Config::getInstance()->getBackupFilename($revision);
if (!$filename) {
Config::getInstance()->unlock();
return ["status" => gettext("unknown (or removed) savepoint")];
}
$this->getModel()->rollback($revision);
Config::getInstance()->unlock();
(new Backend())->configdRun('filter reload');
return ["status" => "ok"];
} else {
return array("status" => "error");
}
}
}
@@ -27,15 +27,9 @@
*/
namespace OPNsense\Firewall\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
class FilterController extends ApiMutableModelControllerBase
class FilterController extends FilterBaseController
{
protected static $internalModelName = 'filter';
protected static $internalModelClass = 'OPNsense\Firewall\Filter';
public function searchRuleAction()
{
return $this->searchBase("rules.rule", array('enabled', 'sequence', 'description'), "sequence");
@@ -65,61 +59,4 @@ class FilterController extends ApiMutableModelControllerBase
{
return $this->toggleBase("rules.rule", $uuid, $enabled);
}
public function applyAction($rollback_revision = null)
{
if ($this->request->isPost()) {
if ($rollback_revision != null) {
// background rollback timer
(new Backend())->configdpRun('pfplugin rollback_timer', [$rollback_revision], true);
}
return array("status" => (new Backend())->configdRun('filter reload'));
} else {
return array("status" => "error");
}
}
public function cancelRollbackAction($rollback_revision)
{
if ($this->request->isPost()) {
return array(
"status" => (new Backend())->configdpRun('pfplugin cancel_rollback', [$rollback_revision])
);
} else {
return array("status" => "error");
}
}
public function savepointAction()
{
if ($this->request->isPost()) {
// trigger a save, so we know revision->time matches our running config
Config::getInstance()->save();
return array(
"status" => "ok",
"retention" => (string)Config::getInstance()->backupCount(),
"revision" => (string)Config::getInstance()->object()->revision->time
);
} else {
return array("status" => "error");
}
}
public function revertAction($revision)
{
if ($this->request->isPost()) {
Config::getInstance()->lock();
$filename = Config::getInstance()->getBackupFilename($revision);
if (!$filename) {
Config::getInstance()->unlock();
return ["status" => gettext("unknown (or removed) savepoint")];
}
$this->getModel()->rollback($revision);
Config::getInstance()->unlock();
(new Backend())->configdRun('filter reload');
return ["status" => "ok"];
} else {
return array("status" => "error");
}
}
}
@@ -0,0 +1,62 @@
<?php
/*
* Copyright (C) 2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Firewall\Api;
class SourceNatController extends FilterBaseController
{
public function searchRuleAction()
{
return $this->searchBase("snatrules.rule", array('enabled', 'sequence', 'description'), "sequence");
}
public function setRuleAction($uuid)
{
return $this->setBase("rule", "snatrules.rule", $uuid);
}
public function addRuleAction()
{
return $this->addBase("rule", "snatrules.rule");
}
public function getRuleAction($uuid = null)
{
return $this->getBase("rule", "snatrules.rule", $uuid);
}
public function delRuleAction($uuid)
{
return $this->delBase("snatrules.rule", $uuid);
}
public function toggleRuleAction($uuid, $enabled = null)
{
return $this->toggleBase("snatrules.rule", $uuid, $enabled);
}
}
@@ -32,6 +32,7 @@ class FilterController extends \OPNsense\Base\IndexController
public function indexAction()
{
$this->view->pick('OPNsense/Firewall/filter');
$this->view->ruleController = "filter";
$this->view->formDialogFilterRule = $this->getForm("dialogFilterRule");
}
}
@@ -0,0 +1,38 @@
<?php
/*
* Copyright (C) 2020 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Firewall;
class SourceNatController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Firewall/filter');
$this->view->ruleController = "source_nat";
$this->view->formDialogFilterRule = $this->getForm("dialogSNatRule");
}
}
@@ -0,0 +1,88 @@
<form>
<field>
<id>rule.enabled</id>
<label>enabled</label>
<type>checkbox</type>
<help>Enable this rule</help>
</field>
<field>
<id>rule.sequence</id>
<label>Sequence</label>
<type>text</type>
</field>
<field>
<id>rule.interface</id>
<label>Interface</label>
<type>dropdown</type>
</field>
<field>
<id>rule.ipprotocol</id>
<label>TCP/IP Version</label>
<type>dropdown</type>
</field>
<field>
<id>rule.protocol</id>
<label>Protocol</label>
<type>dropdown</type>
</field>
<field>
<id>rule.source_net</id>
<label>Source</label>
<type>text</type>
</field>
<field>
<id>rule.source_port</id>
<label>Source port</label>
<type>text</type>
<advanced>true</advanced>
<help>Source port number or well known name (imap, imaps, http, https, ...), for ranges use a dash</help>
</field>
<field>
<id>rule.source_not</id>
<label>Source / Invert</label>
<type>checkbox</type>
<help>Use this option to invert the sense of the match.</help>
</field>
<field>
<id>rule.destination_net</id>
<label>Destination</label>
<type>text</type>
</field>
<field>
<id>rule.destination_not</id>
<label>Destination / Invert</label>
<type>checkbox</type>
<help>Use this option to invert the sense of the match.</help>
</field>
<field>
<id>rule.destination_port</id>
<label>Destination port</label>
<type>text</type>
<help>Destination port number or well known name (imap, imaps, http, https, ...), for ranges use a dash</help>
</field>
<field>
<id>rule.target</id>
<label>Translation / target</label>
<type>text</type>
<help>
Packets matching this rule will be mapped to the IP address given here.
</help>
</field>
<field>
<id>rule.target_port</id>
<label>Translation port</label>
<type>text</type>
<help>Destination port number or well known name (imap, imaps, http, https, ...)</help>
</field>
<field>
<id>rule.log</id>
<label>Log</label>
<type>checkbox</type>
<help>Log packets that are handled by this rule</help>
</field>
<field>
<id>rule.description</id>
<label>Description</label>
<type>text</type>
</field>
</form>
@@ -6,4 +6,11 @@
<pattern>api/firewall/filter/*</pattern>
</patterns>
</page-filter-api>
<page-filter-snat-api>
<name>Firewall: SourceNat: API</name>
<patterns>
<pattern>ui/firewall/source_nat/*</pattern>
<pattern>api/firewall/source_nat/*</pattern>
</patterns>
</page-filter-snat-api>
</acl>
@@ -106,5 +106,80 @@
</description>
</rule>
</rules>
<snatrules>
<rule type="ArrayField">
<enabled type="BooleanField">
<default>1</default>
<Required>Y</Required>
</enabled>
<sequence type="IntegerField">
<MinimumValue>1</MinimumValue>
<MaximumValue>99999</MaximumValue>
<ValidationMessage>provide a valid sequence for sorting</ValidationMessage>
<Required>Y</Required>
<default>1</default>
</sequence>
<interface type="InterfaceField">
<Required>Y</Required>
<default>lan</default>
<AllowDynamic>Y</AllowDynamic>
</interface>
<ipprotocol type="OptionField">
<Required>Y</Required>
<default>inet</default>
<OptionValues>
<inet>IPv4</inet>
<inet6>IPv6</inet6>
</OptionValues>
</ipprotocol>
<protocol type="ProtocolField">
<Required>Y</Required>
<default>any</default>
</protocol>
<source_net type="NetworkAliasField">
<default>any</default>
<Required>Y</Required>
</source_net>
<source_not type="BooleanField">
<default>0</default>
<Required>Y</Required>
</source_not>
<source_port type="PortField">
<Required>N</Required>
<EnableWellKnown>Y</EnableWellKnown>
<EnableRanges>Y</EnableRanges>
</source_port>
<destination_net type="NetworkAliasField">
<default>any</default>
<Required>Y</Required>
</destination_net>
<destination_not type="BooleanField">
<default>0</default>
<Required>Y</Required>
</destination_not>
<destination_port type="PortField">
<Required>N</Required>
<EnableWellKnown>Y</EnableWellKnown>
<EnableRanges>Y</EnableRanges>
</destination_port>
<target type="NetworkAliasField">
<default>wanip</default>
<Required>Y</Required>
</target>
<target_port type="PortField">
<Required>N</Required>
<EnableWellKnown>Y</EnableWellKnown>
</target_port>
<log type="BooleanField">
<default>0</default>
<Required>Y</Required>
</log>
<description type="TextField">
<Required>N</Required>
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.\-,_\x{00A0}-\x{FFFF}]){0,255}$/u</mask>
<ValidationMessage>Description should be a string between 1 and 255 characters</ValidationMessage>
</description>
</rule>
</snatrules>
</items>
</model>
@@ -4,6 +4,9 @@
<Filter order="50" url="/ui/firewall/filter/">
<FilterRef url="/ui/firewall/filter#*"/>
</Filter>
<SourceNat order="100" VisibleName="Source NAT" url="/ui/firewall/source_nat/">
<FilterRef url="/ui/firewall/source_nat#*"/>
</SourceNat>
</Automation>
</Firewall>
</menu>
@@ -2,12 +2,12 @@
$( document ).ready(function() {
let initial_load = true;
let grid = $("#grid-rules").UIBootgrid({
search:'/api/firewall/filter/searchRule/',
get:'/api/firewall/filter/getRule/',
set:'/api/firewall/filter/setRule/',
add:'/api/firewall/filter/addRule/',
del:'/api/firewall/filter/delRule/',
toggle:'/api/firewall/filter/toggleRule/'
search:'/api/firewall/{{ruleController}}/searchRule/',
get:'/api/firewall/{{ruleController}}/getRule/',
set:'/api/firewall/{{ruleController}}/setRule/',
add:'/api/firewall/{{ruleController}}/addRule/',
del:'/api/firewall/{{ruleController}}/delRule/',
toggle:'/api/firewall/{{ruleController}}/toggleRule/'
});
// open edit dialog when opened with a uuid reference
@@ -44,7 +44,7 @@
label: "{{ lang._('Revert') }}",
cssClass: 'btn-primary',
action: function(dialogRef) {
ajaxCall("/api/firewall/filter/revert/" + $("#revertToTime").val(), {}, function (data, status) {
ajaxCall("/api/firewall/{{ruleController}}/revert/" + $("#revertToTime").val(), {}, function (data, status) {
if (data.status !== "ok") {
$("#revertToTime").parent().addClass("has-error");
$("#revertToTimeError").html(data.status);
@@ -71,7 +71,7 @@
</ul>
<div class="tab-content content-box">
<div id="rules" class="tab-pane fade in active">
<!-- tab page "filter rules" -->
<!-- tab page "rules" -->
<table id="grid-rules" class="table table-condensed table-hover table-striped" data-editDialog="DialogFilterRule" data-editAlert="FilterRuleChangeMessage">
<thead>
<tr>
@@ -100,7 +100,7 @@
</div>
<hr/>
<button class="btn btn-primary" id="reconfigureAct"
data-endpoint='/api/firewall/filter/apply'
data-endpoint='/api/firewall/{{ruleController}}/apply'
data-label="{{ lang._('Apply') }}"
data-error-title="{{ lang._('Filter load error') }}"
type="button"
@@ -108,7 +108,7 @@
<div class="pull-right">
<button class="btn" id="savepointAct"
data-endpoint='/api/firewall/filter/savepoint'
data-endpoint='/api/firewall/{{ruleController}}/savepoint'
data-label="{{ lang._('Savepoint') }}"
data-error-title="{{ lang._('snapshot error') }}"
type="button"