This commit is contained in:
Ad Schellevis 2024-01-07 16:56:35 +01:00
parent 3daff54655
commit 8e299d3efe
22 changed files with 1971 additions and 0 deletions

View File

@ -0,0 +1,48 @@
<?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.
*/
/**
* @param $fw
*/
function pfplugin_firewall($fw)
{
$mdlFilter = new OPNsense\Firewall\Filter();
foreach ($mdlFilter->rules->rule->sortedBy(["sequence"]) as $key => $rule) {
$content = $rule->serialize();
$content["#ref"] = "ui/firewall/filter#" . (string)$rule->getAttributes()['uuid'];
$fw->registerFilterRule($rule->getPriority(), $content);
}
foreach ($mdlFilter->snatrules->rule->sortedBy(["sequence"]) as $key => $rule) {
$fw->registerSNatRule(50, $rule->serialize());
}
foreach ($mdlFilter->npt->rule->sortedBy(["sequence"]) as $key => $rule) {
$fw->registerNptRule(50, $rule->serialize());
}
}

View File

@ -0,0 +1,178 @@
<?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;
use OPNsense\Firewall\Alias;
use OPNsense\Firewall\Category;
/**
* Class FilterBaseController implements actions for various types
* @package OPNsense\Firewall\Api
*/
abstract class FilterBaseController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'filter';
protected static $internalModelClass = 'OPNsense\Firewall\Filter';
protected static $categorysource = null;
/**
* list categories and usage
* @return array
*/
public function listCategoriesAction()
{
$response = ['rows' => []];
$catcount = [];
if (!empty(static::$categorysource)) {
$node = $this->getModel();
foreach (explode('.', static::$categorysource) as $ref) {
$node = $node->$ref;
}
foreach ($node->iterateItems() as $item) {
if (!empty((string)$item->categories)) {
foreach (explode(',', (string)$item->categories) as $cat) {
if (!isset($catcount[$cat])) {
$catcount[$cat] = 0;
}
$catcount[$cat] += 1;
}
}
}
}
foreach ((new Category())->categories->category->iterateItems() as $key => $category) {
$response['rows'][] = [
"uuid" => $key,
"name" => (string)$category->name,
"color" => (string)$category->color,
"used" => isset($catcount[$key]) ? $catcount[$key] : 0
];
}
array_multisort(array_column($response['rows'], "name"), SORT_ASC, SORT_NATURAL, $response['rows']);
return $response;
}
/**
* list of available network options
* @return array
*/
public function listNetworkSelectOptionsAction()
{
$result = [
'single' => [
'label' => gettext("Single host or Network")
],
'aliases' => [
'label' => gettext("Aliases"),
'items' => []
],
'networks' => [
'label' => gettext("Networks"),
'items' => [
'any' => gettext('any'),
'(self)' => gettext("This Firewall")
]
]
];
foreach ((Config::getInstance()->object())->interfaces->children() as $ifname => $ifdetail) {
$descr = htmlspecialchars(!empty($ifdetail->descr) ? $ifdetail->descr : strtoupper($ifname));
$result['networks']['items'][$ifname] = $descr . " " . gettext("net");
if (!isset($ifdetail->virtual)) {
$result['networks']['items'][$ifname . "ip"] = $descr . " " . gettext("address");
}
}
foreach ((new Alias())->aliases->alias->iterateItems() as $alias) {
if (strpos((string)$alias->type, "port") === false) {
$result['aliases']['items'][(string)$alias->name] = (string)$alias->name;
}
}
return $result;
}
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");
}
}
}

View File

@ -0,0 +1,67 @@
<?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 FilterController extends FilterBaseController
{
protected static $categorysource = "rules.rule";
public function searchRuleAction()
{
$category = $this->request->get('category');
$filter_funct = function ($record) use ($category) {
return empty($category) || array_intersect(explode(',', $record->categories), $category);
};
return $this->searchBase("rules.rule", ['enabled', 'sequence', 'description'], "sequence", $filter_funct);
}
public function setRuleAction($uuid)
{
return $this->setBase("rule", "rules.rule", $uuid);
}
public function addRuleAction()
{
return $this->addBase("rule", "rules.rule");
}
public function getRuleAction($uuid = null)
{
return $this->getBase("rule", "rules.rule", $uuid);
}
public function delRuleAction($uuid)
{
return $this->delBase("rules.rule", $uuid);
}
public function toggleRuleAction($uuid, $enabled = null)
{
return $this->toggleBase("rules.rule", $uuid, $enabled);
}
}

View File

@ -0,0 +1,72 @@
<?php
/*
* Copyright (C) 2023 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 NptController extends FilterBaseController
{
protected static $categorysource = "npt.rule";
public function searchRuleAction()
{
$category = $this->request->get('category');
$filter_funct = function ($record) use ($category) {
return empty($category) || array_intersect(explode(',', $record->categories), $category);
};
return $this->searchBase(
"npt.rule",
['enabled', 'sequence', 'source_net', 'destination_net', 'trackif', 'description'],
"sequence",
$filter_funct
);
}
public function setRuleAction($uuid)
{
return $this->setBase("rule", "npt.rule", $uuid);
}
public function addRuleAction()
{
return $this->addBase("rule", "npt.rule");
}
public function getRuleAction($uuid = null)
{
return $this->getBase("rule", "npt.rule", $uuid);
}
public function delRuleAction($uuid)
{
return $this->delBase("npt.rule", $uuid);
}
public function toggleRuleAction($uuid, $enabled = null)
{
return $this->toggleBase("npt.rule", $uuid, $enabled);
}
}

View File

@ -0,0 +1,67 @@
<?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
{
protected static $categorysource = "snatrules.rule";
public function searchRuleAction()
{
$category = $this->request->get('category');
$filter_funct = function ($record) use ($category) {
return empty($category) || array_intersect(explode(',', $record->categories), $category);
};
return $this->searchBase("snatrules.rule", ['enabled', 'sequence', 'description'], "sequence", $filter_funct);
}
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);
}
}

View File

@ -0,0 +1,49 @@
<?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 FilterController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Firewall/filter');
$this->view->ruleController = "filter";
$this->view->gridFields = [
[
'id' => 'enabled', 'formatter' => 'rowtoggle' ,'width' => '6em', 'heading' => gettext('Enabled')
],
[
'id' => 'sequence','width' => '9em', 'heading' => gettext('Sequence')
],
[
'id' => 'description', 'heading' => gettext('Description')
]
];
$this->view->formDialogFilterRule = $this->getForm("dialogFilterRule");
}
}

View File

@ -0,0 +1,60 @@
<?php
/*
* Copyright (C) 2023 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 NptController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Firewall/filter');
$this->view->ruleController = "npt";
$this->view->hideSavePointBtns = true;
$this->view->gridFields = [
[
'id' => 'enabled', 'formatter' => 'rowtoggle' ,'width' => '6em', 'heading' => gettext('Enabled')
],
[
'id' => 'sequence','width' => '9em', 'heading' => gettext('Sequence')
],
[
'id' => 'source_net', 'heading' => gettext('Internal IPv6 Prefix')
],
[
'id' => 'destination_net', 'heading' => gettext('External IPv6 Prefix')
],
[
'id' => 'trackif', 'heading' => gettext('Track if')
],
[
'id' => 'description', 'heading' => gettext('Description')
]
];
$this->view->formDialogFilterRule = $this->getForm("dialogNptRule");
}
}

View File

@ -0,0 +1,49 @@
<?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->gridFields = [
[
'id' => 'enabled', 'formatter' => 'rowtoggle' ,'width' => '6em', 'heading' => gettext('Enabled')
],
[
'id' => 'sequence','width' => '9em', 'heading' => gettext('Sequence')
],
[
'id' => 'description', 'heading' => gettext('Description')
]
];
$this->view->formDialogFilterRule = $this->getForm("dialogSNatRule");
}
}

View File

@ -0,0 +1,114 @@
<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.action</id>
<label>Action</label>
<type>dropdown</type>
<help>Choose what to do with packets that match the criteria specified below.
Hint: the difference between block and reject is that with reject, a packet (TCP RST or ICMP port unreachable for UDP) is returned to the sender, whereas with block the packet is dropped silently. In either case, the original packet is discarded.
</help>
</field>
<field>
<id>rule.quick</id>
<label>Quick</label>
<type>checkbox</type>
<help>
If a packet matches a rule specifying quick, then that rule is considered the last matching rule and the specified action is taken.
When a rule does not have quick enabled, the last matching rule wins.
</help>
</field>
<field>
<id>rule.interface</id>
<label>Interface</label>
<type>select_multiple</type>
</field>
<field>
<id>rule.direction</id>
<label>Direction</label>
<type>dropdown</type>
<help>
Direction of the traffic. The default policy is to filter inbound traffic, which sets the policy to the interface originally receiving the traffic.
</help>
</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.gateway</id>
<label>Gateway</label>
<type>dropdown</type>
<help>
Leave as 'default' to use the system routing table. Or choose a gateway to utilize policy based routing.
</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.categories</id>
<label>Categories</label>
<type>select_multiple</type>
<style>tokenize</style>
<help>For grouping purposes you may select multiple groups here to organize items.</help>
</field>
<field>
<id>rule.description</id>
<label>Description</label>
<type>text</type>
</field>
</form>

View File

@ -0,0 +1,53 @@
<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.log</id>
<label>Log</label>
<type>checkbox</type>
<help>Log packets that are handled by this rule</help>
</field>
<field>
<id>rule.interface</id>
<label>Interface</label>
<type>dropdown</type>
</field>
<field>
<id>rule.source_net</id>
<label>Internal IPv6 Prefix (source)</label>
<type>text</type>
</field>
<field>
<id>rule.destination_net</id>
<label>External IPv6 Prefix (target)</label>
<type>text</type>
<help>Enter the external IPv6 prefix for this network prefix translation. Leave empty to auto-detect the prefix address using the specified tracking interface instead. The prefix size specified for the internal prefix will also be applied to the external prefix.</help>
</field>
<field>
<id>rule.trackif</id>
<label>Track interface</label>
<type>dropdown</type>
<help>Use prefix defined on the selected interface instead of the interface this rule applies to when target prefix is not provided.</help>
</field>
<field>
<id>rule.categories</id>
<label>Categories</label>
<type>select_multiple</type>
<style>tokenize</style>
<help>For grouping purposes you may select multiple groups here to organize items.</help>
</field>
<field>
<id>rule.description</id>
<label>Description</label>
<type>text</type>
</field>
</form>

View File

@ -0,0 +1,101 @@
<form>
<field>
<id>rule.enabled</id>
<label>enabled</label>
<type>checkbox</type>
<help>Enable this rule</help>
</field>
<field>
<id>rule.nonat</id>
<label>Do not NAT</label>
<type>checkbox</type>
<help>Enabling this option will disable NAT for traffic matching this rule and stop processing Outbound NAT rules.</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.categories</id>
<label>Categories</label>
<type>select_multiple</type>
<style>tokenize</style>
<help>For grouping purposes you may select multiple groups here to organize items.</help>
</field>
<field>
<id>rule.description</id>
<label>Description</label>
<type>text</type>
</field>
</form>

View File

@ -0,0 +1,16 @@
<acl>
<page-filter-api>
<name>Firewall: Rules: API</name>
<patterns>
<pattern>ui/firewall/filter/*</pattern>
<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>

View File

@ -0,0 +1,140 @@
<?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\FieldTypes;
use OPNsense\Core\Config;
use OPNsense\Base\FieldTypes\ArrayField;
use OPNsense\Base\FieldTypes\ContainerField;
/**
* Class FilterRuleContainerField
* @package OPNsense\Firewall\FieldTypes
*/
class FilterRuleContainerField extends ContainerField
{
/**
* map rules
* @return array
*/
public function serialize()
{
$result = array();
$map_manual = ['source_net', 'source_not', 'source_port', 'destination_net', 'destination_not',
'destination_port', 'enabled', 'description', 'sequence', 'action'];
// 1-on-1 map (with type conversion if needed)
foreach ($this->iterateItems() as $key => $node) {
if (!in_array($key, $map_manual)) {
if (is_a($node, "OPNsense\\Base\\FieldTypes\\BooleanField")) {
$result[$key] = !empty((string)$node);
} elseif (is_a($node, "OPNsense\\Base\\FieldTypes\\ProtocolField")) {
if ((string)$node != 'any') {
$result[$key] = (string)$node;
}
} else {
$result[$key] = (string)$node;
}
}
}
// source / destination mapping
$result['source'] = array();
if (!empty((string)$this->source_net)) {
$result['source']['network'] = (string)$this->source_net;
if (!empty((string)$this->source_not)) {
$result['source']['not'] = true;
}
if (!empty((string)$this->source_port)) {
$result['source']['port'] = (string)$this->source_port;
}
}
$result['destination'] = array();
if (!empty((string)$this->destination_net)) {
$result['destination']['network'] = (string)$this->destination_net;
if (!empty((string)$this->destination_not)) {
$result['destination']['not'] = true;
}
if (!empty((string)$this->destination_port)) {
$result['destination']['port'] = (string)$this->destination_port;
}
}
// field mappings and differences
$result['disabled'] = empty((string)$this->enabled);
$result['descr'] = (string)$this->description;
$result['type'] = (string)$this->action;
if (strpos((string)$this->interface, ",") !== false) {
$result['floating'] = true;
}
return $result;
}
/**
* rule priority is threaded equally to the legacy rules, first "floating" then groups and single interface
* rules are handled last
* @return int priority in the ruleset, sequence should determine sort order.
*/
public function getPriority()
{
$configObj = Config::getInstance()->object();
$interface = (string)$this->interface;
if (strpos($interface, ",") !== false) {
// floating (multiple interfaces involved)
return 1000;
} elseif (
!empty($configObj->interfaces) &&
!empty($configObj->interfaces->$interface) &&
!empty($configObj->interfaces->$interface->type) &&
$configObj->interfaces->$interface->type == 'group'
) {
// group type
return 2000;
} else {
// default
return 3000;
}
}
}
/**
* Class FilterRuleField
* @package OPNsense\Firewall\FieldTypes
*/
class FilterRuleField extends ArrayField
{
/**
* @inheritDoc
*/
public function newContainerField($ref, $tagname)
{
$container_node = new FilterRuleContainerField($ref, $tagname);
$parentmodel = $this->getParentModel();
$container_node->setParentModel($parentmodel);
return $container_node;
}
}

View File

@ -0,0 +1,113 @@
<?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\FieldTypes;
use OPNsense\Base\FieldTypes\ArrayField;
use OPNsense\Base\FieldTypes\ContainerField;
/**
* Class SourceNatRuleContainerField
* @package OPNsense\Firewall\FieldTypes
*/
class SourceNatRuleContainerField extends ContainerField
{
/**
* map source nat rules
* @return array
*/
public function serialize()
{
$result = [];
$source_mapper = [
'enabled' => false,
'source_net' => false,
'source_not' => false,
'source_port' => 'sourceport',
'destination_net' => false,
'destination_not' => false,
'destination_port' => 'dstport',
'target_port' => 'natport',
'description' => 'descr'
];
// 1-on-1 map (with type conversion if needed)
foreach ($this->iterateItems() as $key => $node) {
$target_fieldname = isset($source_mapper[$key]) ? $source_mapper[$key] : $key;
if ($target_fieldname) {
if (is_a($node, "OPNsense\\Base\\FieldTypes\\BooleanField")) {
$result[$target_fieldname] = !empty((string)$node);
} elseif (is_a($node, "OPNsense\\Base\\FieldTypes\\ProtocolField")) {
if ((string)$node != 'any') {
$result[$target_fieldname] = (string)$node;
}
} else {
$result[$target_fieldname] = (string)$node;
}
}
}
$result['disabled'] = empty((string)$this->enabled);
// source / destination mapping, doesn't use port construct like it would for rules.
$result['source'] = array();
if (!empty((string)$this->source_net)) {
$result['source']['network'] = (string)$this->source_net;
if (!empty((string)$this->source_not)) {
$result['source']['not'] = true;
}
}
$result['destination'] = array();
if (!empty((string)$this->destination_net)) {
$result['destination']['network'] = (string)$this->destination_net;
if (!empty((string)$this->destination_not)) {
$result['destination']['not'] = true;
}
}
return $result;
}
}
/**
* Class SourceNatRuleField
* @package OPNsense\Firewall\FieldTypes
*/
class SourceNatRuleField extends ArrayField
{
/**
* @inheritDoc
*/
public function newContainerField($ref, $tagname)
{
$container_node = new SourceNatRuleContainerField($ref, $tagname);
$parentmodel = $this->getParentModel();
$container_node->setParentModel($parentmodel);
return $container_node;
}
}

View File

@ -0,0 +1,145 @@
<?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;
use OPNsense\Core\Config;
use Phalcon\Messages\Message;
use OPNsense\Base\BaseModel;
use OPNsense\Firewall\Util;
class Filter extends BaseModel
{
/**
* @inheritDoc
*/
public function performValidation($validateFullModel = false)
{
// standard model validations
$messages = parent::performValidation($validateFullModel);
foreach ([$this->rules->rule, $this->snatrules->rule] as $rules) {
foreach ($rules->iterateItems() as $rule) {
if ($validateFullModel || $rule->isFieldChanged()) {
// port / protocol validation
if (!empty((string)$rule->source_port) && !in_array($rule->protocol, ['TCP', 'UDP'])) {
$messages->appendMessage(new Message(
gettext("Source ports are only valid for tcp or udp type rules."),
$rule->source_port->__reference
));
}
if (!empty((string)$rule->destination_port) && !in_array($rule->protocol, ['TCP', 'UDP'])) {
$messages->appendMessage(new Message(
gettext("Destination ports are only valid for tcp or udp type rules."),
$rule->destination_port->__reference
));
}
// validate protocol family
$dest_is_addr = Util::isSubnet($rule->destination_net) || Util::isIpAddress($rule->destination_net);
$dest_proto = strpos($rule->destination_net, ':') === false ? "inet" : "inet6";
if ($dest_is_addr && $dest_proto != $rule->ipprotocol) {
$messages->appendMessage(new Message(
gettext("Destination address type should match selected TCP/IP protocol version."),
$rule->destination_net->__reference
));
}
$src_is_addr = Util::isSubnet($rule->source_net) || Util::isIpAddress($rule->source_net);
$src_proto = strpos($rule->source_net, ':') === false ? "inet" : "inet6";
if ($src_is_addr && $src_proto != $rule->ipprotocol) {
$messages->appendMessage(new Message(
gettext("Source address type should match selected TCP/IP protocol version."),
$rule->source_net->__reference
));
}
// Additional source nat validations
if ($rule->target !== null) {
$target_is_addr = Util::isSubnet($rule->target) || Util::isIpAddress($rule->target);
$target_proto = strpos($rule->target, ':') === false ? "inet" : "inet6";
if ($target_is_addr && $target_proto != $rule->ipprotocol) {
$messages->appendMessage(new Message(
gettext("Target address type should match selected TCP/IP protocol version."),
$rule->target->__reference
));
}
if (!empty((string)$rule->target_port) && !in_array($rule->protocol, ['TCP', 'UDP'])) {
$messages->appendMessage(new Message(
gettext("Target ports are only valid for tcp or udp type rules."),
$rule->target_port->__reference
));
}
}
}
}
}
foreach ($this->npt->rule->iterateItems() as $rule) {
if ($validateFullModel || $rule->isFieldChanged()) {
if (!empty((string)$rule->destination_net) && !empty((string)$rule->trackif)) {
$messages->appendMessage(new Message(
gettext("A track interface is only allowed without an extrenal prefix."),
$rule->trackif->__reference
));
}
if (!empty((string)$rule->destination_net) && !empty((string)$rule->source_net)) {
$dparts = explode('/', (string)$rule->destination_net);
$sparts = explode('/', (string)$rule->source_net);
if (count($dparts) == 2 && count($sparts) == 2 && $dparts[1] != $sparts[1]) {
$messages->appendMessage(new Message(
gettext("External subnet should match internal subnet."),
$rule->destination_net->__reference
));
}
}
}
}
return $messages;
}
/**
* Rollback this model to a previous version.
* Make sure to remove this object afterwards, since its contents won't be updated.
* @param $revision float|string revision number
* @return bool action performed (backup revision existed)
*/
public function rollback($revision)
{
$filename = Config::getInstance()->getBackupFilename($revision);
if ($filename) {
// fiddle with the dom, copy OPNsense->Firewall->Filter from backup to current config
$sourcexml = simplexml_load_file($filename);
if ($sourcexml->OPNsense->Firewall->Filter) {
$sourcedom = dom_import_simplexml($sourcexml->OPNsense->Firewall->Filter);
$targetxml = Config::getInstance()->object();
$targetdom = dom_import_simplexml($targetxml->OPNsense->Firewall->Filter);
$node = $targetdom->ownerDocument->importNode($sourcedom, true);
$targetdom->parentNode->replaceChild($node, $targetdom);
Config::getInstance()->save();
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,270 @@
<model>
<mount>//OPNsense/Firewall/Filter</mount>
<version>1.0.2</version>
<migration_prefix>MFP</migration_prefix>
<description>
OPNsense firewall filter rules
</description>
<items>
<rules>
<rule type=".\FilterRuleField">
<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>
<action type="OptionField">
<Required>Y</Required>
<Default>pass</Default>
<OptionValues>
<pass>Pass</pass>
<block>Block</block>
<reject>Reject</reject>
</OptionValues>
</action>
<quick type="BooleanField">
<Default>1</Default>
<Required>Y</Required>
</quick>
<interface type="InterfaceField">
<Required>N</Required>
<Multiple>Y</Multiple>
<AllowDynamic>Y</AllowDynamic>
</interface>
<direction type="OptionField">
<Required>Y</Required>
<Default>in</Default>
<OptionValues>
<in>In</in>
<out>Out</out>
</OptionValues>
</direction>
<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>
<!-- XXX: should map internally to 'source' => array('network' => $source_net, "not" => true|false) -->
<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>
<EnableAlias>Y</EnableAlias>
<ValidationMessage>Please specify a valid portnumber, name, alias or range</ValidationMessage>
</source_port>
<!-- XXX: should map internally to 'source' => array('destination' => destination_net, "not" => true|false) -->
<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>
<EnableAlias>Y</EnableAlias>
<ValidationMessage>Please specify a valid portnumber, name, alias or range</ValidationMessage>
</destination_port>
<gateway type="JsonKeyValueStoreField">
<Required>N</Required>
<ConfigdPopulateAct>interface gateways list</ConfigdPopulateAct>
<SourceFile>/tmp/gateway_list.json</SourceFile>
<ConfigdPopulateTTL>20</ConfigdPopulateTTL>
<ValidationMessage>Specify a valid gateway from the list matching the networks ip protocol.</ValidationMessage>
</gateway>
<log type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</log>
<categories type="ModelRelationField">
<Model>
<rulesets>
<source>OPNsense.Firewall.Category</source>
<items>categories.category</items>
<display>name</display>
</rulesets>
</Model>
<Multiple>Y</Multiple>
<ValidationMessage>Related category not found.</ValidationMessage>
</categories>
<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>
</rules>
<snatrules>
<rule type=".\SourceNatRuleField">
<enabled type="BooleanField">
<Default>1</Default>
<Required>Y</Required>
</enabled>
<nonat type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</nonat>
<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>
<EnableAlias>Y</EnableAlias>
<ValidationMessage>Please specify a valid portnumber, name, alias or range</ValidationMessage>
</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>
<EnableAlias>Y</EnableAlias>
<ValidationMessage>Please specify a valid portnumber, name, alias or range</ValidationMessage>
</destination_port>
<target type="NetworkAliasField">
<Default>wanip</Default>
<Required>Y</Required>
</target>
<target_port type="PortField">
<Required>N</Required>
<EnableWellKnown>Y</EnableWellKnown>
<EnableRanges>Y</EnableRanges>
</target_port>
<log type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</log>
<categories type="ModelRelationField">
<Model>
<rulesets>
<source>OPNsense.Firewall.Category</source>
<items>categories.category</items>
<display>name</display>
</rulesets>
</Model>
<Multiple>Y</Multiple>
<ValidationMessage>Related category not found.</ValidationMessage>
</categories>
<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>
<npt>
<rule type=".\SourceNatRuleField">
<enabled type="BooleanField">
<Default>1</Default>
<Required>Y</Required>
</enabled>
<log type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</log>
<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>
<source_net type="NetworkField">
<Required>Y</Required>
<AddressFamily>ipv6</AddressFamily>
<NetMaskRequired>Y</NetMaskRequired>
<WildcardEnabled>N</WildcardEnabled>
</source_net>
<destination_net type="NetworkField">
<AddressFamily>ipv6</AddressFamily>
<NetMaskRequired>Y</NetMaskRequired>
<WildcardEnabled>N</WildcardEnabled>
</destination_net>
<trackif type="InterfaceField">
</trackif>
<categories type="ModelRelationField">
<Model>
<rulesets>
<source>OPNsense.Firewall.Category</source>
<items>categories.category</items>
<display>name</display>
</rulesets>
</Model>
<Multiple>Y</Multiple>
<ValidationMessage>Related category not found.</ValidationMessage>
</categories>
<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>
</npt>
</items>
</model>

View File

@ -0,0 +1,15 @@
<menu>
<Firewall>
<Automation order="1000" cssClass="fa fa-gear">
<Filter order="50" url="/ui/firewall/filter/">
<FilterRef url="/ui/firewall/filter#*" visibility="hidden"/>
</Filter>
<SourceNat order="100" VisibleName="Source NAT" url="/ui/firewall/source_nat/">
<FilterRef url="/ui/firewall/source_nat#*" visibility="hidden"/>
</SourceNat>
<Npt order="100" VisibleName="NPTv6" url="/ui/firewall/npt/">
<FilterRef url="/ui/firewall/npt#*" visibility="hidden"/>
</Npt>
</Automation>
</Firewall>
</menu>

View File

@ -0,0 +1,59 @@
<?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\Migrations;
use OPNsense\Core\Config;
use OPNsense\Base\BaseModelMigration;
class MFP1_0_0 extends BaseModelMigration
{
public function post($model)
{
// Move OPNsense->Firewall->FilterRule ---> OPNsense->Firewall->Filter
$cfgObj = Config::getInstance()->object();
if (
!empty($cfgObj->OPNsense) && !empty($cfgObj->OPNsense->Firewall)
&& !empty($cfgObj->OPNsense->Firewall->FilterRule)
) {
// model migration created a new, empty rules section
if (empty($cfgObj->OPNsense->Firewall->Filter->rules)) {
unset($cfgObj->OPNsense->Firewall->Filter->rules);
$targetdom = dom_import_simplexml($cfgObj->OPNsense->Firewall->Filter);
foreach ($cfgObj->OPNsense->Firewall->FilterRule->children() as $child) {
$sourcedom = dom_import_simplexml($child);
$targetdom->appendChild($sourcedom);
}
unset($cfgObj->OPNsense->Firewall->FilterRule);
Config::getInstance()->save();
}
}
}
}

View File

@ -0,0 +1,250 @@
<script>
$( document ).ready(function() {
let initial_load = true;
let grid = $("#grid-rules").UIBootgrid({
search:'/api/firewall/{{ruleController}}/search_rule/',
get:'/api/firewall/{{ruleController}}/get_rule/',
set:'/api/firewall/{{ruleController}}/set_rule/',
add:'/api/firewall/{{ruleController}}/add_rule/',
del:'/api/firewall/{{ruleController}}/del_rule/',
toggle:'/api/firewall/{{ruleController}}/toggle_rule/',
options:{
requestHandler: function(request){
if ( $('#category_filter').val().length > 0) {
request['category'] = $('#category_filter').val();
}
return request;
}
}
});
grid.on("loaded.rs.jquery.bootgrid", function (e){
// reload categories before grid load
ajaxCall('/api/firewall/{{ruleController}}/list_categories', {}, function(data, status){
if (data.rows !== undefined) {
let current_selection = $("#category_filter").val();
$("#category_filter").empty();
for (i=0; i < data.rows.length ; ++i) {
let row = data.rows[i];
let opt_val = $('<div/>').html(row.name).text();
let bgcolor = row.color != "" ? row.color : '31708f;'; // set category color
let option = $("<option/>").val(row.uuid).html(row.name);
if (row.used > 0) {
option.attr(
'data-content',
"<span>"+opt_val + "</span>"+
"<span style='background:#"+bgcolor+";' class='badge pull-right'>" + row.used + "</span>"
);
option.attr('id', row.uuid);
}
$("#category_filter").append(option);
}
$("#category_filter").val(current_selection);
$("#category_filter").selectpicker('refresh');
}
});
});
// open edit dialog when opened with a uuid reference
if (window.location.hash !== "" && window.location.hash.split("-").length >= 4) {
grid.on('loaded.rs.jquery.bootgrid', function(){
if (initial_load) {
$(".command-edit:eq(0)").clone(true).data('row-id', window.location.hash.substr(1)).click();
initial_load = false;
}
});
}
$("#reconfigureAct").SimpleActionButton();
$("#savepointAct").SimpleActionButton({
onAction: function(data, status){
stdDialogInform(
"{{ lang._('Savepoint created') }}",
data['revision'],
"{{ lang._('Close') }}"
);
}
});
$("#revertAction").on('click', function(){
BootstrapDialog.show({
type: BootstrapDialog.TYPE_DEFAULT,
title: "{{ lang._('Revert to savepoint') }}",
message: "<p>{{ lang._('Enter a savepoint to rollback to.') }}</p>" +
'<div class="form-group" style="display: block;">' +
'<input id="revertToTime" type="text" class="form-control"/>' +
'<span class="error text-danger" id="revertToTimeError"></span>'+
'</div>',
buttons: [{
label: "{{ lang._('Revert') }}",
cssClass: 'btn-primary',
action: function(dialogRef) {
ajaxCall("/api/firewall/{{ruleController}}/revert/" + $("#revertToTime").val(), {}, function (data, status) {
if (data.status !== "ok") {
$("#revertToTime").parent().addClass("has-error");
$("#revertToTimeError").html(data.status);
} else {
std_bootgrid_reload("grid-rules");
dialogRef.close();
}
});
}
}],
onshown: function(dialogRef) {
$("#revertToTime").parent().removeClass("has-error");
$("#revertToTimeError").html("");
$("#revertToTime").val("");
}
});
});
// move filter into action header
$("#type_filter_container").detach().prependTo('#grid-rules-header > .row > .actionBar > .actions');
$("#category_filter").change(function(){
$('#grid-rules').bootgrid('reload');
});
// replace all "net" selectors with details retrieved from "list_network_select_options" endpoint
ajaxGet('/api/firewall/{{ruleController}}/list_network_select_options', [], function(data, status){
// fetch options
let options = [];
if (data.single) {
Object.keys(data).forEach((key, idx) => {
if (data[key].items !== undefined) {
let optgrp = $("<optgroup/>").attr('label', data[key].label);
Object.keys(data[key].items).forEach((key2, idx2) => {
let this_item = data[key].items[key2];
optgrp.append($("<option/>").val(key2).text(this_item));
});
options.push(optgrp);
} else {
options.push($("<option/>").val('').text(data[key].label));
}
});
}
if (options.length == 0) {
// unable to fetch options.
return;
}
$(".net_selector").each(function(){
let $items = $("#network_select").clone().show();
let $this_input = $items.find('input');
let $this_select = $items.find('select');
for (i=0; i < options.length; ++i) {
$this_select.append(options[i].clone());
}
$this_select.attr('for', $(this).attr('id')).selectpicker();
$this_select.change(function(){
let $value = $(this).val();
if ($value !== '') {
$this_input.val($value);
$this_input.hide();
} else {
$this_input.show();
}
});
$this_input.attr('id', $(this).attr('id'));
$this_input.change(function(){
$this_select.val($(this).val());
if ($this_select.val() === null || $this_select.val() == '') {
$this_select.val('');
$this_input.show();
} else {
$this_input.hide();
}
$this_select.selectpicker('refresh');
});
$this_input.show();
$(this).replaceWith($items);
});
});
});
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#rules">{{ lang._('Rules') }}</a></li>
</ul>
<div class="tab-content content-box">
<div id="rules" class="tab-pane fade in active">
<div class="hidden">
<!-- filter per type container -->
<div id="type_filter_container" class="btn-group">
<select id="category_filter" data-title="{{ lang._('Categories') }}" class="selectpicker" data-live-search="true" data-size="5" multiple data-width="200px">
</select>
</div>
</div>
<!-- tab page "rules" -->
<table id="grid-rules" class="table table-condensed table-hover table-striped" data-editDialog="DialogFilterRule" data-editAlert="FilterRuleChangeMessage">
<thead>
<tr>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
{% for fieldlist in gridFields %}
<th
data-column-id="{{fieldlist['id']}}"
data-width="{{fieldlist['width']|default('')}}"
data-type="{{fieldlist['type']|default('string')}}"
data-formatter="{{fieldlist['formatter']|default('')}}"
>{{fieldlist['heading']|default('')}}</th>
{% endfor %}
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<div class="col-md-12">
<div id="FilterRuleChangeMessage" class="alert alert-info" style="display: none" role="alert">
{{ lang._('After changing settings, please remember to apply them with the button below') }}
</div>
<hr/>
<button class="btn btn-primary" id="reconfigureAct"
data-endpoint='/api/firewall/{{ruleController}}/apply'
data-label="{{ lang._('Apply') }}"
data-error-title="{{ lang._('Filter load error') }}"
type="button"
></button>
{% if not hideSavePointBtns|default(false) %}
<div class="pull-right">
<button class="btn" id="savepointAct"
data-endpoint='/api/firewall/{{ruleController}}/savepoint'
data-label="{{ lang._('Savepoint') }}"
data-error-title="{{ lang._('snapshot error') }}"
type="button"
></button>
<button class="btn" id="revertAction">
{{ lang._('Revert') }}
</button>
</div>
{% endif %}
<br/><br/>
</div>
</div>
</div>
<div id="network_select" style="display: none;" >
<table style="max-width: 348px">
<tr>
<td>
<select data-live-search="true" data-size="5" data-width="348px">
</select>
</td>
</tr>
<tr>
<td>
<input style="display:none;" type="text"/>
</td>
</tr>
</table>
</div>
{{ partial("layout_partials/base_dialog",['fields':formDialogFilterRule,'id':'DialogFilterRule','label':lang._('Edit rule')])}}

View File

@ -0,0 +1,40 @@
#!/usr/local/bin/php
<?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.
*/
if (count($argv) >= 2) {
$revision = preg_replace("/[^0-9.]/", "", $argv[1]);
if (!empty($revision)) {
$lckfile = "/tmp/pfplugin_{$revision}.lock";
if (file_exists($lckfile)) {
unlink($lckfile);
exit(0);
}
}
}
exit(1);

View File

@ -0,0 +1,54 @@
#!/usr/local/bin/php
<?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.
*/
require_once('script/load_phalcon.php');
if (count($argv) >= 2) {
$revision = preg_replace("/[^0-9.]/", "", $argv[1]);
if (!empty($revision)) {
$lckfile = "/tmp/pfplugin_{$revision}.lock";
file_put_contents($lckfile, "");
// give the api 60 seconds to callback
for ($i=0; $i < 60 ; ++$i) {
if (!file_exists($lckfile)) {
// got feedback
exit(0);
}
sleep(1);
}
@unlink($lckfile);
// no feedback, revert
$mdlFilter = new OPNsense\Firewall\Filter();
if ($mdlFilter->rollback($revision)) {
(new OPNsense\Core\Backend())->configdRun('filter reload');
} else {
syslog(LOG_WARNING, "unable to revert to unexisting revision : {$revision}");
}
}
}

View File

@ -0,0 +1,11 @@
[rollback_timer]
command:/usr/local/bin/flock -n -E 0 -o /tmp/pfplugin_rollback_timer.lock /usr/local/opnsense/scripts/pfplugin/rollback_timer
parameters: %s
type:script
message:wait for api feedback or revert to previous filter plugin config
[cancel_rollback]
command: /usr/local/opnsense/scripts/pfplugin/rollback_cancel
parameters: %s
type:script_output
message:cancel pfplugin rollback