dns/bind: move to primary/secondary (#3234)

This commit is contained in:
Robbert Rijkse 2023-01-11 03:52:32 -05:00 committed by GitHub
parent 3c74322935
commit 18a4256267
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 164 additions and 70 deletions

View file

@ -1,5 +1,5 @@
PLUGIN_NAME= bind
PLUGIN_VERSION= 1.25
PLUGIN_VERSION= 1.26
PLUGIN_COMMENT= BIND domain name service
PLUGIN_DEPENDS= bind918
PLUGIN_MAINTAINER= m.muenz@gmail.com

View file

@ -9,6 +9,10 @@ WWW: https://www.isc.org
Plugin Changelog
================
1.26
* Rename Master/Slave to Primary/Secondary (contributed by Robbert Rijkse)
1.25
* Ensure you can only add one ACL with the same name (contributed by Robbert Rijkse)

View file

@ -39,26 +39,36 @@ class DomainController extends ApiMutableModelControllerBase
protected static $internalModelName = 'domain';
protected static $internalModelClass = '\OPNsense\Bind\Domain';
# These are here for backwards compatability, should be removed after a bit.
public function searchMasterDomainAction()
{
return seachPrimaryDomainAction();
}
public function searchSlaveDomainAction()
{
return seachSecondaryDomainAction();
}
public function searchPrimaryDomainAction()
{
return $this->searchBase(
'domains.domain',
[ "enabled", "type", "domainname", "ttl", "refresh", "retry", "expire", "negative" ],
"domainname",
function ($record) {
return $record->type->getNodeData()["master"]["selected"] === 1;
return $record->type->getNodeData()["primary"]["selected"] === 1;
}
);
}
public function searchSlaveDomainAction()
public function searchSecondaryDomainAction()
{
return $this->searchBase(
'domains.domain',
[ "enabled", "type", "domainname", "masterip" ],
[ "enabled", "type", "domainname", "primaryip" ],
"domainname",
function ($record) {
return $record->type->getNodeData()["slave"]["selected"] === 1;
return $record->type->getNodeData()["secondary"]["selected"] === 1;
}
);
}
@ -69,14 +79,14 @@ class DomainController extends ApiMutableModelControllerBase
return $this->getBase('domain', 'domains.domain', $uuid);
}
public function addMasterDomainAction($uuid = null)
public function addPrimaryDomainAction($uuid = null)
{
return $this->addBase('domain', 'domains.domain', ['type' => 'master']);
return $this->addBase('domain', 'domains.domain', ['type' => 'primary']);
}
public function addSlaveDomainAction($uuid = null)
public function addSecondaryDomainAction($uuid = null)
{
return $this->addBase('domain', 'domains.domain', ['type' => 'slave']);
return $this->addBase('domain', 'domains.domain', ['type' => 'secondary']);
}
public function delDomainAction($uuid)

View file

@ -36,8 +36,8 @@ class GeneralController extends \OPNsense\Base\IndexController
$this->view->dnsblForm = $this->getForm("dnsbl");
$this->view->rndcKeyForm = $this->getForm("rndcKey");
$this->view->formDialogEditBindAcl = $this->getForm("dialogEditBindAcl");
$this->view->formDialogEditBindMasterDomain = $this->getForm("dialogEditBindMasterDomain");
$this->view->formDialogEditBindSlaveDomain = $this->getForm("dialogEditBindSlaveDomain");
$this->view->formDialogEditBindPrimaryDomain = $this->getForm("dialogEditBindPrimaryDomain");
$this->view->formDialogEditBindSecondaryDomain = $this->getForm("dialogEditBindSecondaryDomain");
$this->view->formDialogEditBindRecord = $this->getForm("dialogEditBindRecord");
$this->view->pick('OPNsense/Bind/general');
}

View file

@ -24,24 +24,24 @@
<help>Define an ACL where you allow which client are allowed to query this zone.</help>
</field>
<field>
<id>domain.masterip</id>
<label>Master IP</label>
<id>domain.primaryip</id>
<label>Primary IP</label>
<style>tokenize</style>
<type>select_multiple</type>
<allownew>true</allownew>
<help>Set the IP address of master server.</help>
<help>Set the IP address of primary server.</help>
</field>
<field>
<id>domain.transferkeyalgo</id>
<label>Transfer Key Algorithm</label>
<type>dropdown</type>
<help>Set the authentication algorithm for the TSIG key used to transfer domain data from the master server.</help>
<help>Set the authentication algorithm for the TSIG key used to transfer domain data from the primary server.</help>
</field>
<field>
<id>domain.transferkeyname</id>
<label>Transfer Key Name</label>
<type>text</type>
<help>The name of the TSIG key, which must match the value on the master server.</help>
<help>The name of the TSIG key, which must match the value on the primary server.</help>
</field>
<field>
<id>domain.transferkey</id>
@ -50,11 +50,11 @@
<help>The TSIG key used to transfer domain data from the master server in Base64 encoding.</help>
</field>
<field>
<id>domain.allownotifyslave</id>
<id>domain.allownotifysecondary</id>
<label>Allow Notfiy</label>
<style>tokenize</style>
<type>select_multiple</type>
<allownew>true</allownew>
<help>A list of allowed IP addresses to receive notifies from (in addition to the master server).</help>
<help>A list of allowed IP addresses to receive notifies from (in addition to the primary server.)</help>
</field>
</form>

View file

@ -1,7 +1,7 @@
<model>
<mount>//OPNsense/bind/domain</mount>
<description>BIND domain configuration</description>
<version>1.0.1</version>
<version>1.1.0</version>
<items>
<domains>
<domain type="ArrayField">
@ -10,18 +10,18 @@
<Required>Y</Required>
</enabled>
<type type="OptionField">
<default>master</default>
<default>primary</default>
<Required>Y</Required>
<OptionValues>
<master>master</master>
<slave>slave</slave>
<primary>Primary</primary>
<secondary>Secondary</secondary>
</OptionValues>
</type>
<masterip type="NetworkField">
<primaryip type="NetworkField">
<Required>N</Required>
<FieldSeparator>,</FieldSeparator>
<asList>Y</asList>
</masterip>
</primaryip>
<transferkeyalgo type="OptionField">
<Required>N</Required>
<OptionValues>
@ -41,11 +41,11 @@
<Required>N</Required>
<default></default>
</transferkey>
<allownotifyslave type="NetworkField">
<allownotifysecondary type="NetworkField">
<Required>N</Required>
<FieldSeparator>,</FieldSeparator>
<asList>Y</asList>
</allownotifyslave>
</allownotifysecondary>
<domainname type="TextField">
<default></default>
<Required>Y</Required>

View file

@ -0,0 +1,80 @@
<?php
/**
* Copyright (C) 2022 Robbert Rijkse
*
* 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\Bind\Migrations;
use OPNsense\Base\BaseModelMigration;
use OPNsense\Core\Config;
use OPNsense\Bind\Domain;
class M1_1_0 extends BaseModelMigration
{
/**
* Migrate older keys into new model
* @param $model
*/
public function run($model)
{
if ($model instanceof Domain) {
$config = Config::getInstance()->object();
# Checks to see if there is a bind config section, otherwise skips the rest of the migration
if (empty($config->OPNsense->bind)) {
return;
}
$bindConfig = $config->OPNsense->bind;
# Loops through the domains in the config
foreach ($bindConfig->domain->domains->domain as $domain) {
$domainModel = $model->getNodeByReference('domains.domain.' . $domain->attributes()["uuid"]);
# Migrates the domain type
if ($domain->type == "master") {
$domainModel->type->setValue("primary");
} else {
$domainModel->type->setValue("secondary");
}
# Migrates the Master IP to Primary IP field
if (!empty($domain->masterip)) {
$domainModel->primaryip->setValue($domain->masterip);
}
# Migrates the AllowNotify Slave to AllowNotify Secondary field
if (!empty($domain->allownotifyslave)) {
$domainModel->allownotifysecondary->setValue($domain->allownotifyslave);
}
}
parent::run($model);
}
}
}

View file

@ -33,8 +33,8 @@ POSSIBILITY OF SUCH DAMAGE.
<li><a data-toggle="tab" href="#dnsbl">{{ lang._('DNSBL') }}</a></li>
<li><a data-toggle="tab" href="#acls">{{ lang._('ACLs') }}</a></li>
<li><a data-toggle="tab" href="#keys">{{ lang._('Keys') }}</a></li>
<li><a data-toggle="tab" href="#master-domains">{{ lang._('Master Zones') }}</a></li>
<li><a data-toggle="tab" href="#slave-domains">{{ lang._('Slave Zones') }}</a></li>
<li><a data-toggle="tab" href="#primary-domains">{{ lang._('Primary Zones') }}</a></li>
<li><a data-toggle="tab" href="#secondary-domains">{{ lang._('Secondary Zones') }}</a></li>
</ul>
<div class="tab-content content-box tab-content">
@ -85,7 +85,7 @@ POSSIBILITY OF SUCH DAMAGE.
<br /><br />
</div>
</div>
<div id="keys" class="tab-pane fade in">
<div id="keys" class="tab-pane fade in">
<div class="content-box">
<div class="col-md-12">
<h2>{{ lang._('RNDC Key') }}</h2>
@ -100,11 +100,11 @@ POSSIBILITY OF SUCH DAMAGE.
<br /><br />
</div>
</div>
<div id="master-domains" class="tab-pane fade in">
<div id="primary-domains" class="tab-pane fade in">
<div class="col-md-12">
<h2>{{ lang._('Zones') }}</h2>
</div>
<table id="grid-master-domains" class="table table-condensed table-hover table-striped table-responsive" data-editAlert="ChangeMessage" data-editDialog="dialogEditBindMasterDomain">
<table id="grid-primary-domains" class="table table-condensed table-hover table-striped table-responsive" data-editAlert="ChangeMessage" data-editDialog="dialogEditBindPrimaryDomain">
<thead>
<tr>
<th data-column-id="enabled" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
@ -130,11 +130,11 @@ POSSIBILITY OF SUCH DAMAGE.
</tfoot>
</table>
<hr/>
<div id="master-record-area">
<div id="primary-record-area">
<div class="col-md-12">
<h2>{{ lang._('Records') }}</h2>
</div>
<table id="grid-master-records" class="table table-condensed table-hover table-striped table-responsive" data-editAlert="ChangeMessage" data-editDialog="dialogEditBindRecord">
<table id="grid-primary-records" class="table table-condensed table-hover table-striped table-responsive" data-editAlert="ChangeMessage" data-editDialog="dialogEditBindRecord">
<thead>
<tr>
<th data-column-id="enabled" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
@ -168,16 +168,16 @@ POSSIBILITY OF SUCH DAMAGE.
<br /><br />
</div>
</div>
<div id="slave-domains" class="tab-pane fade in">
<div id="secondary-domains" class="tab-pane fade in">
<div class="col-md-12">
<h2>{{ lang._('Zones') }}</h2>
</div>
<table id="grid-slave-domains" class="table table-condensed table-hover table-striped table-responsive" data-editAlert="ChangeMessage" data-editDialog="dialogEditBindSlaveDomain">
<table id="grid-secondary-domains" class="table table-condensed table-hover table-striped table-responsive" data-editAlert="ChangeMessage" data-editDialog="dialogEditBindSecondaryDomain">
<thead>
<tr>
<th data-column-id="enabled" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="domainname" data-type="string" data-visible="true">{{ lang._('Zone') }}</th>
<th data-column-id="masterip" data-type="string" data-visible="true">{{ lang._('Master IPs') }}</th>
<th data-column-id="primaryip" data-type="string" data-visible="true">{{ lang._('Primary IPs') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
<th data-column-id="commands" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
@ -206,8 +206,8 @@ POSSIBILITY OF SUCH DAMAGE.
</div>
{{ partial("layout_partials/base_dialog",['fields':formDialogEditBindAcl,'id':'dialogEditBindAcl','label':lang._('Edit ACL')])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogEditBindMasterDomain,'id':'dialogEditBindMasterDomain','label':lang._('Edit Master Zone')])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogEditBindSlaveDomain,'id':'dialogEditBindSlaveDomain','label':lang._('Edit Slave Zone')])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogEditBindPrimaryDomain,'id':'dialogEditBindPrimaryDomain','label':lang._('Edit Primary Zone')])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogEditBindSecondaryDomain,'id':'dialogEditBindSecondaryDomain','label':lang._('Edit Secondary Zone')])}}
{{ partial("layout_partials/base_dialog",['fields':formDialogEditBindRecord,'id':'dialogEditBindRecord','label':lang._('Edit Record')])}}
<script>
@ -236,11 +236,11 @@ $( document ).ready(function() {
}
);
$("#grid-master-domains").UIBootgrid({
'search':'/api/bind/domain/searchMasterDomain',
$("#grid-primary-domains").UIBootgrid({
'search':'/api/bind/domain/searchPrimaryDomain',
'get':'/api/bind/domain/getDomain/',
'set':'/api/bind/domain/setDomain/',
'add':'/api/bind/domain/addMasterDomain/',
'add':'/api/bind/domain/addPrimaryDomain/',
'del':'/api/bind/domain/delDomain/',
'toggle':'/api/bind/domain/toggleDomain/',
options:{
@ -250,21 +250,21 @@ $( document ).ready(function() {
rowCount: [3,7,14,20,50,100,-1]
}
}).on("selected.rs.jquery.bootgrid", function(e, rows) {
$("#grid-master-records").bootgrid('reload');
$("#grid-primary-records").bootgrid('reload');
}).on("deselected.rs.jquery.bootgrid", function(e, rows) {
$("#grid-master-records").bootgrid('reload');
$("#grid-primary-records").bootgrid('reload');
}).on("loaded.rs.jquery.bootgrid", function (e) {
let ids = $("#grid-master-domains").bootgrid("getCurrentRows");
let ids = $("#grid-primary-domains").bootgrid("getCurrentRows");
if (ids.length > 0) {
$("#grid-master-domains").bootgrid('select', [ids[0].uuid]);
$("#grid-primary-domains").bootgrid('select', [ids[0].uuid]);
}
});
$("#grid-slave-domains").UIBootgrid({
'search':'/api/bind/domain/searchSlaveDomain',
$("#grid-secondary-domains").UIBootgrid({
'search':'/api/bind/domain/searchSecondaryDomain',
'get':'/api/bind/domain/getDomain/',
'set':'/api/bind/domain/setDomain/',
'add':'/api/bind/domain/addSlaveDomain/',
'add':'/api/bind/domain/addSecondaryDomain/',
'del':'/api/bind/domain/delDomain/',
'toggle':'/api/bind/domain/toggleDomain/',
options:{
@ -274,13 +274,13 @@ $( document ).ready(function() {
rowCount: [7,14,20,50,100,-1]
}
}).on("loaded.rs.jquery.bootgrid", function (e) {
let ids = $("#grid-slave-domains").bootgrid("getCurrentRows");
let ids = $("#grid-secondary-domains").bootgrid("getCurrentRows");
if (ids.length > 0) {
$("#grid-slave-domains").bootgrid('select', [ids[0].uuid]);
$("#grid-secondary-domains").bootgrid('select', [ids[0].uuid]);
}
});
$("#grid-master-records").UIBootgrid({
$("#grid-primary-records").UIBootgrid({
'search':'/api/bind/record/searchRecord',
'get':'/api/bind/record/getRecord/',
'set':'/api/bind/record/setRecord/',
@ -290,17 +290,17 @@ $( document ).ready(function() {
options:{
useRequestHandlerOnGet: true,
requestHandler: function(request) {
let ids = $("#grid-master-domains").bootgrid("getSelectedRows");
let ids = $("#grid-primary-domains").bootgrid("getSelectedRows");
if (ids.length > 0) {
request['domain'] = ids[0];
$("#recordAddBtn").show();
$("#recordDelBtn").show();
$("#master-record-area").show();
$("#primary-record-area").show();
} else {
request['domain'] = 'not_found';
$("#recordAddBtn").hide();
$("#recordDelBtn").hide();
$("#master-record-area").hide();
$("#primary-record-area").hide();
}
return request;
}

View file

@ -2,7 +2,7 @@
{% if helpers.exists('OPNsense.bind.domain.domains.domain') %}
{% for domaindb in helpers.toList('OPNsense.bind.domain.domains.domain') %}
{% if TARGET_FILTERS['OPNsense.bind.domain.domains.domain.' ~ loop.index0] or TARGET_FILTERS['OPNsense.bind.domain.domains.domain'] %}
{% if domaindb.enabled == '1' and domaindb.type == 'master' %}
{% if domaindb.enabled == '1' and domaindb.type == 'primary' %}
$TTL {{ domaindb.ttl }}
@ IN SOA {{ domaindb.dnsserver }}. {{ domaindb.mailadmin|replace('@', '.') }}. ( {{ domaindb.serial|trim }} {{ domaindb.refresh }} {{ domaindb.retry }} {{ domaindb.expire }} {{ domaindb.negative }} )
{% for record in helpers.sortDictList(OPNsense.bind.record.records.record, 'name', 'type' ) %}

View file

@ -101,34 +101,34 @@ controls {
zone "." { type hint; file "/usr/local/etc/namedb/named.root"; };
zone "localhost" { type master; file "/usr/local/etc/namedb/primary/localhost-forward.db"; };
zone "127.in-addr.arpa" { type master; file "/usr/local/etc/namedb/primary/localhost-reverse.db"; };
zone "0.ip6.arpa" { type master; file "/usr/local/etc/namedb/primary/localhost-reverse.db"; };
zone "localhost" { type primary; file "/usr/local/etc/namedb/primary/localhost-forward.db"; };
zone "127.in-addr.arpa" { type primary; file "/usr/local/etc/namedb/primary/localhost-reverse.db"; };
zone "0.ip6.arpa" { type primary; file "/usr/local/etc/namedb/primary/localhost-reverse.db"; };
{% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %}
{% if helpers.exists('OPNsense.bind.dnsbl.type') and OPNsense.bind.dnsbl.type != '' %}
zone "whitelist.localdomain" { type master; file "/usr/local/etc/namedb/primary/whitelist.db"; notify no; check-names ignore; };
zone "blacklist.localdomain" { type master; file "/usr/local/etc/namedb/primary/blacklist.db"; notify no; check-names ignore; };
zone "whitelist.localdomain" { type primary; file "/usr/local/etc/namedb/primary/whitelist.db"; notify no; check-names ignore; };
zone "blacklist.localdomain" { type primary; file "/usr/local/etc/namedb/primary/blacklist.db"; notify no; check-names ignore; };
{% endif %}
{% endif %}
{% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %}
{% if helpers.exists('OPNsense.bind.dnsbl.forcesafegoogle') and OPNsense.bind.dnsbl.forcesafegoogle == '1' %}
zone "rpzgoogle" { type master; file "/usr/local/etc/namedb/primary/google.db"; notify no; check-names ignore; };
zone "rpzgoogle" { type primary; file "/usr/local/etc/namedb/primary/google.db"; notify no; check-names ignore; };
{% endif %}
{% endif %}
{% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %}
{% if helpers.exists('OPNsense.bind.dnsbl.forcesafeduckduckgo') and OPNsense.bind.dnsbl.forcesafeduckduckgo == '1' %}
zone "rpzduckduckgo" { type master; file "/usr/local/etc/namedb/primary/duckduckgo.db"; notify no; check-names ignore; };
zone "rpzduckduckgo" { type primary; file "/usr/local/etc/namedb/primary/duckduckgo.db"; notify no; check-names ignore; };
{% endif %}
{% endif %}
{% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %}
{% if helpers.exists('OPNsense.bind.dnsbl.forcesafeyoutube') and OPNsense.bind.dnsbl.forcesafeyoutube == '1' %}
zone "rpzyoutube" { type master; file "/usr/local/etc/namedb/primary/youtube.db"; notify no; check-names ignore; };
zone "rpzyoutube" { type primary; file "/usr/local/etc/namedb/primary/youtube.db"; notify no; check-names ignore; };
{% endif %}
{% endif %}
{% if helpers.exists('OPNsense.bind.dnsbl.enabled') and OPNsense.bind.dnsbl.enabled == '1' %}
{% if helpers.exists('OPNsense.bind.dnsbl.forcestrictbing') and OPNsense.bind.dnsbl.forcestrictbing == '1' %}
zone "rpzbing" { type master; file "/usr/local/etc/namedb/primary/bing.db"; notify no; check-names ignore; };
zone "rpzbing" { type primary; file "/usr/local/etc/namedb/primary/bing.db"; notify no; check-names ignore; };
{% endif %}
{% endif %}
@ -140,14 +140,14 @@ zone "rpzbing" { type master; file "/usr/local/etc/namedb/primary/bing.db"; noti
{% set allow_query = helpers.getUUID(domain.allowquery) %}
zone "{{ domain.domainname }}" {
type {{ domain.type }};
{% if domain.type == 'slave' %}
{% if domain.type == 'secondary' %}
{% if domain.transferkey is defined %}
masters { {{ domain.masterip.replace(',', ' key "' ~ domain.transferkeyname ~ '"; ') }} key "{{ domain.transferkeyname }}"; };
primaries { {{ domain.primaryip.replace(',', ' key "' ~ domain.transferkeyname ~ '"; ') }} key "{{ domain.transferkeyname }}"; };
{% else %}
masters { {{ domain.masterip.replace(',', '; ') }}; };
primaries { {{ domain.primaryip.replace(',', '; ') }}; };
{% endif %}
{% if domain.allownotifyslave is defined %}
allow-notify { {{ domain.allownotifyslave.replace(',', '; ') }}; };
{% if domain.allownotifysecondary is defined %}
allow-notify { {{ domain.allownotifysecondary.replace(',', '; ') }}; };
{% endif %}
file "/usr/local/etc/namedb/secondary/{{ domain.domainname }}.db";
{% else %}
@ -160,7 +160,7 @@ zone "{{ domain.domainname }}" {
allow-query { {{ allow_query.name }}; };
{% endif %}
};
{% if domain.type == 'slave' and domain.transferkey is defined and not(domain.transferkeyname in usedkeys) %}
{% if domain.type == 'secondary' and domain.transferkey is defined and not(domain.transferkeyname in usedkeys) %}
{% do usedkeys.append(domain.transferkeyname) %}
key "{{ domain.transferkeyname }}" {
algorithm "{{ domain.transferkeyalgo }}";