Services: Unbound DNS: Overrides - move domain overrides to Query Forwarding, closes https://github.com/opnsense/core/issues/7243

This commit is contained in:
Ad Schellevis 2024-10-22 11:23:36 +02:00
parent e33c4ab513
commit aa8fe94ce0
8 changed files with 21 additions and 268 deletions

View File

@ -266,44 +266,6 @@ class SettingsController extends ApiMutableModelControllerBase
return $this->toggleBase('aliases.alias', $uuid, $enabled);
}
/* Domain overrides */
public function searchDomainOverrideAction()
{
return $this->searchBase(
'domains.domain',
['enabled', 'domain', 'server', 'description'],
"domain",
null,
SORT_NATURAL | SORT_FLAG_CASE
);
}
public function getDomainOverrideAction($uuid = null)
{
return $this->getBase('domain', 'domains.domain', $uuid);
}
public function addDomainOverrideAction()
{
return $this->addBase('domain', 'domains.domain');
}
public function delDomainOverrideAction($uuid)
{
return $this->delBase('domains.domain', $uuid);
}
public function setDomainOverrideAction($uuid)
{
return $this->setBase('domain', 'domains.domain', $uuid);
}
public function toggleDomainOverrideAction($uuid, $enabled = null)
{
return $this->toggleBase('domains.domain', $uuid, $enabled);
}
/* ACLs */
public function searchAclAction()

View File

@ -1,7 +1,7 @@
<?php
/*
* Copyright (C) 2022 Stephan de Wit <stephan.de.wit@deciso.com>
* Copyright (C) 2022-2024 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -37,6 +37,5 @@ class OverridesController extends IndexController
$this->view->pick('OPNsense/Unbound/overrides');
$this->view->formDialogHostOverride = $this->getForm("dialogHostOverride");
$this->view->formDialogHostAlias = $this->getForm("dialogHostAlias");
$this->view->formDialogDomainOverride = $this->getForm("dialogDomainOverride");
}
}

View File

@ -1,44 +0,0 @@
<form>
<field>
<id>domain.enabled</id>
<label>Enabled</label>
<type>checkbox</type>
<help>Enable this domain override</help>
</field>
<field>
<id>domain.domain</id>
<label>Domain</label>
<type>text</type>
<help>
Domain to override (NOTE: this does not have to be a valid TLD!),
e.g. 'test' or 'mycompany.localdomain' or '1.168.192.in-addr.arpa'
</help>
</field>
<field>
<id>domain.server</id>
<label>IP address</label>
<type>text</type>
<help>
IP address of the authoritative DNS server for this domain,
e.g. '192.168.100.100'. To use a non-default port for communication,
append an '@' with the port number.
</help>
</field>
<field>
<id>domain.forward_tcp_upstream</id>
<label>Forward TCP upstream</label>
<type>checkbox</type>
<advanced>true</advanced>
<help>
Upstream queries use TCP only for transport regardless of global flag tcp-upstream.
Please note this setting applies to the domain, so when multiple forwarders are defined for the same domain,
all are assumed to use tcp only.
</help>
</field>
<field>
<id>domain.description</id>
<label>Description</label>
<type>text</type>
<help>You may enter a description here for your reference (not parsed).</help>
</field>
</form>

View File

@ -1,71 +0,0 @@
<?php
/**
* Copyright (C) 2022 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\Unbound\FieldTypes;
use OPNsense\Base\FieldTypes\BaseField;
use OPNsense\Base\Validators\CallbackValidator;
use OPNsense\Firewall\Util;
/**
* Class UnboundDomainField
* @package OPNsense\Unbound\FieldTypes
*/
class UnboundDomainField extends BaseField
{
protected $internalIsContainer = false;
public function getValidators()
{
/**
* XXX: the only reason this class exists is to be compatible with
* legacy code allowing '_msdcs' domain prefixes. This is weird, not
* conforming to any standard and should probably be looked into further.
*/
$validators = parent::getValidators();
if ($this->internalValue != null) {
$validators[] = new CallbackValidator([
"callback" => function ($data) {
if (substr($data, 0, 6) == '_msdcs') {
$subdomain = substr($data, 7);
if (substr($data, 6, 1) != '.' || !Util::isDomain($subdomain)) {
return [gettext("A valid domain must be specified after '_msdcs', prefixed with a dot")];
}
} elseif (!Util::isDomain($data)) {
return [gettext("A valid domain must be specified")];
}
return [];
}
]);
}
return $validators;
}
}

View File

@ -1,7 +1,7 @@
<?php
/*
* Copyright (C) 2022 Deciso B.V.
* Copyright (C) 2023 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -26,42 +26,28 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Unbound\FieldTypes;
namespace OPNsense\Unbound\Migrations;
use OPNsense\Base\FieldTypes\BaseField;
use OPNsense\Base\Validators\CallbackValidator;
use OPNsense\Firewall\Util;
use OPNsense\Base\BaseModelMigration;
use OPNsense\Core\Config;
/**
* Class UnboundServerField
* @package OPNsense\Unbound\FieldTypes
*/
class UnboundServerField extends BaseField
class M1_0_11 extends BaseModelMigration
{
/**
* {@inheritdoc}
*/
protected $internalIsContainer = false;
/**
* {@inheritdoc}
*/
public function getValidators()
public function run($model)
{
$validators = parent::getValidators();
if ($this->internalValue != null) {
$validators[] = new CallbackValidator([
"callback" => function ($value) {
$parts = explode("@", $value);
if (count($parts) == 2 && (!Util::isIpAddress($parts[0]) || !Util::isPort($parts[1]))) {
return [gettext("A valid IP address and port must be specified, for example 192.168.100.10@5353.")];
} elseif (count($parts) != 2 && !Util::isIpAddress($value)) {
return [gettext("A valid IP address must be specified, for example 192.168.100.10.")];
}
return [];
$config = Config::getInstance()->object();
if (isset($config->OPNsense->unboundplus) && isset($config->OPNsense->unboundplus->domains->domain)) {
foreach ($config->OPNsense->unboundplus->domains->children() as $domain) {
$new_item = $model->dots->dot->Add();
$new_item->enabled = (string)$domain->enabled;
$new_item->type = 'forward';
$new_item->domain = (string)$domain->domain;
$parts = explode('@', (string)$domain->server);
$new_item->server = $parts[0];
if (isset($parts[1])) {
$new_item->port = $parts[1];
}
]);
}
}
return $validators;
}
}

View File

@ -1,7 +1,7 @@
<model>
<mount>//OPNsense/unboundplus</mount>
<description>Unbound configuration</description>
<version>1.0.10</version>
<version>1.0.11</version>
<items>
<general>
<enabled type="BooleanField">
@ -390,26 +390,5 @@
<description type="DescriptionField"/>
</alias>
</aliases>
<domains>
<domain type="ArrayField">
<enabled type="BooleanField">
<Default>1</Default>
<Required>Y</Required>
</enabled>
<domain type=".\UnboundDomainField">
<Required>Y</Required>
<ValidationMessage>A valid domain must be specified.</ValidationMessage>
</domain>
<server type=".\UnboundServerField">
<Required>Y</Required>
<ValidationMessage>A valid IP must be specified.</ValidationMessage>
</server>
<forward_tcp_upstream type="BooleanField">
<Default>0</Default>
<Required>Y</Required>
</forward_tcp_upstream>
<description type="DescriptionField"/>
</domain>
</domains>
</items>
</model>

View File

@ -185,7 +185,6 @@ $( document ).ready(function() {
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li><a data-toggle="tab" href="#host_overrides" id="host_overrides_tab">{{ lang._('Host Overrides') }}</a></li>
<li><a data-toggle="tab" href="#domain_overrides" id="domain_overrides_tab">{{ lang._('Domain Overrides') }}</a></li>
</ul>
<div class="tab-content content-box col-xs-12 __mb">
<!-- host overrides -->
@ -220,35 +219,6 @@ $( document ).ready(function() {
{{ lang._('Keep in mind that all resource record types (i.e. A, AAAA, MX, etc. records) of a specified host below are being overwritten.') }}
</div>
</div>
<!-- domain overrides -->
<div id="domain_overrides" class="tab-pane fade in">
<table id="grid-domains" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogDomainOverride" data-editAlert="OverrideChangeMessage">
<thead>
<tr>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
<th data-column-id="enabled" data-width="6em" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="domain" data-type="string">{{ lang._('Domain') }}</th>
<th data-column-id="server" data-type="string">{{ lang._('IP') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Edit') }} | {{ lang._('Delete') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-primary"><span class="fa fa-fw fa-plus"></span></button>
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-fw fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
<div id="infosection" class="tab-content col-xs-12 __mb">
{{ lang._('Entries in this area override an entire domain by specifying an authoritative DNS server to be queried for that domain.') }}
</div>
</div>
</div>
<!-- aliases for host overrides -->
<div class="tab-content content-box col-xs-12 __mb" id ="grid-aliases-wrapper">

View File

@ -1,29 +1 @@
{% if not helpers.empty('OPNsense.unboundplus.domains.domain') %}
{% set forwardlocal = namespace(found=False) %}
{% set domain_opts = namespace(forward_tcp_upstream=False) %}
{% set prev_domain = namespace(name='') %}
{% for domain in helpers.toList('OPNsense.unboundplus.domains.domain', 'domain') %}
{% if domain.enabled == '1' %}
{% if not loop.previtem or prev_domain.name != domain.domain %}
{% set domain_opts.forward_tcp_upstream = False %}
forward-zone:
name: "{{ domain.domain }}"
{% set prev_domain.name = domain.domain %}
{% if domain.server.startswith('127.') or domain.server == '::1' %}
{% set forwardlocal.found = True %}
{% endif %}
{% set domain_opts.forward_tcp_upstream = domain_opts.forward_tcp_upstream or domain.forward_tcp_upstream == '1' %}
{% endif %}
forward-addr: {{ domain.server }}
{% if not loop.nextitem or loop.nextitem.domain != domain.domain %}
{% if domain_opts.forward_tcp_upstream %}
forward-tcp-upstream: yes
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% if forwardlocal.found %}
server:
do-not-query-localhost: no
{% endif %}
{% endif %}
# contents migrated to dot.conf ({empty} file kept intentionally)