This is a drop-in provider for ApisCP to enable DNS support using PowerDNS. This module may use PostgreSQL or MySQL as a backend driver.
Installation can be chosen at install time or after setup. Installation is only necessary if you intend on running a PowerDNS instance on the server. This section covers installation; skip down to ApisCP DNS provider setup for information on configuring a server to use PowerDNS as a DNS provider.
cpcmd scope:set cp.bootstrapper powerdns_enabled true
cpcmd scope:set cp.bootstrapper powerdns_driver mysql
# Or specify "pgsql" to use PostgreSQL
upcp -sb software/powerdns
# Optionally set all accounts to use PowerDNS
cpcmd scope:set dns.default-provider powerdns
::: tip DNS-only licenses ApisCP provides a DNS-only license class that allows ApisCP to run on a server without the capability to host sites. These licenses are free and may be requested via my.apiscp.com. :::
Firewall access is automatically opened inbound for 53/TCP and 53/UDP when PowerDNS is enabled. On CentOS 8+ machines, to avoid a potential service conflict with systemd-resolved, PowerDNS will bind only to the primary IP address. This can be changed by setting powerdns_dns_bind_address
to a comma-separated string of IPv4 and IPv6 addresses. Prior to PowerDNS 4.3, this value may only accept a list of IPv4 addresses.
# Listen on 192.168.0.1 and all IPv6 interfaces on pdns v4.3
cpcmd scope:set cp.bootstrapper powerdns_dns_bind_address '192.168.0.1, ::'
upcp -sb software/powerdns
In Local mode, PowerDNS only accepts API calls that originate locally from the server. This allows you to place PowerDNS' API behind a reverse proxy, such as Apache. Local-only is enabled by default.
PowerDNS is setup to accept requests on port 8081 (powerdns_api_port
setting). Requests require an authorization key that can be found in /etc/pdns/pdns.conf
# Install jq if not already installed
yum install -y jq
# This is your API key
grep '^api-key=' /etc/pdns/pdns.conf | cut -d= -f2
curl -v -H 'X-API-Key: APIKEYABOVE' http://127.0.0.1:8081/api/v1/servers/localhost | jq .
PowerDNS may be configured via files in /etc/pdns/local.d
. In addition to this location, Bootstrapper supports injecting settings via powerdns_custom_config
. For example,
cpcmd scope:set cp.bootstrapper 'powerdns_custom_config' '["allow-axfr-ips":1.2.3.4,"also-notify":1.2.3.4]'
# Then re-run Bootstrapper
upcp -sb software/powerdns
allow-axfr-ips
and also-notify
directives will be set whenever the role is run.
ALIAS is a synthetic record that allows CNAME records to be set on the zone apex. ALIAS records require powerdns_enable_recursion
to be enabled as well as an optional powerdns_recursive_ns
to be set otherwise it will default to the system in /etc/resolv.conf
.
cpcmd scope:set cp.bootstrapper powerdns_enable_recursion true
cpcmd scope:set cp.bootstrapper powerdns_recursive_ns '[1.1.1.1,1.0.0.1]'
# Then re-run Bootstrapper
upcp -sb software/powerdns
powerdns.apiscp.com provides turnkey configration for hidden master and exposed master cluster types discussed below.
Before configuring a cluster, PowerDNS server must be installed and a suitable backend selected. See "Nameserver installation".
PowerDNS uses MySQL or PostgreSQL for record storage when zone kind is "NATIVE". Using AXFR-based replication will allow provisioning of zones to slaves by supermaster but these zones cannot be removed from slaves. AXFR does not provide any means to achieve this in its protocol specification. Workarounds exist as noted in "Zone removal".
Hidden master
A hidden master obfuscates which server has control of publishing DNS records. This provides a marginal security benefit by hiding the server from public view.
In the following example, master is an unpublished nameserver.
On the master, assuming 1.2.3.4 and 1.2.3.5 are slave nameservers with the hostnames ns1.domain.com and ns2.domain.com respectively, add the following configuration:
cpcmd scope:set cp.bootstrapper powerdns_enabled true
cpcmd scope:set cp.bootstrapper powerdns_zone_type master
cpcmd scope:set cp.bootstrapper powerdns_custom_config '["allow-axfr-ips":"1.2.3.4,1.2.3.5","also-notify":"1.2.3.4,1.2.3.5","master":"yes"]'
cpcmd scope:set cp.bootstrapper powerdns_nameservers '[ns1.domain.com,ns2.domain.com]'
cpcmd scope:set dns.default-provider powerdns
upcp -sbf software/powerdns
On the slave(s), assuming the master is 1.2.3.3 with the hostname master.domain.com, add the following configuration:
cpcmd scope:set cp.bootstrapper powerdns_enabled true
cpcmd scope:set cp.bootstrapper powerdns_zone_type slave
cpcmd scope:set cp.bootstrapper powerdns_custom_config '["allow-notify-from":"1.2.3.3","slave":"yes","superslave":"yes"]'
cpcmd scope:set cp.bootstrapper powerdns_nameservers '[ns1.domain.com,ns2.domain.com]'
cpcmd scope:set cp.bootstrapper powerdns_supermaster '[ip:1.2.3.3,nameserver:ns1.domain.com,account:master]'
cpcmd scope:set dns.default-provider powerdns
# Uncomment this if the slave nameserver is also providing hosting services
# cpcmd scope:set cp.bootstrapper powerdns_api_key 'abc1234'
# cpcmd scope:set cp.bootstrapper powerdns_api_uri 'https://master.domain.com/dns/api/v1'
upcp -sbf software/powerdns
Lastly, on the hosting nodes, assuming all DNS zone traffic is sent to the hidden master master.domain.com (IP address 1.2.3.3) with the API key from the hidden master located in /etc/pdns/pdns.conf
of abc1234
, configure each to use the same API key and endpoint discussed below.
cpcmd scope:set cp.bootstrapper powerdns_api_uri 'https://master.domain.com/dns/api/v1'
cpcmd scope:set cp.bootstrapper powerdns_nameservers '[ns1.domain.com,ns2.domain.com]'
cpcmd scope:set cp.bootstrapper powerdns_api_key 'abc1234'
cpcmd scope:set cp.bootstrapper powerdns_zone_type 'master'
cpcmd scope:set dns.default-provider powerdns
upcp -sbf software/powerdns
::: tip force=yes
Bootstrapper will avoid overwriting certain configurations unless explicitly asked. force=yes
is a global variable that forces an overwrite on files.
:::
Be sure to skip down to the Remote API access section to configure the hidden master endpoint.
::: tip Notifying slaves
After making changes on the master, slaves previously unprovisioned must receive a NOTIFY command to create zones. pdns_control notify "*"
will send a NOTIFY command to all slaves for all domains on the master.
:::
In the above, a hidden master is used which obscures the server that handles updates. If we promote primary nameserver to master, which removes a hidden master setup, then the diagram changes slightly.
On the primary nameserver NS1, assuming 1.2.3.5 is a slave nameserver with the nameserver names ns1.domain.com and ns2.domain.com respectively, add the following configuration:
cpcmd scope:set cp.bootstrapper powerdns_enabled true
cpcmd scope:set cp.bootstrapper powerdns_zone_type master
cpcmd scope:set cp.bootstrapper powerdns_custom_config '["allow-axfr-ips":"1.2.3.5","also-notify":"1.2.3.5","master":"yes"]'
cpcmd scope:set cp.bootstrapper powerdns_webserver_enable true
cpcmd scope:set cp.bootstrapper powerdns_nameservers '[ns1.domain.com,ns2.domain.com]'
cpcmd scope:set dns.default-provider powerdns
upcp -sfb software/powerdns
On the slave(s), assuming the master is 1.2.3.4 with the hostname ns1.domain.com, add the following configuration:
cpcmd scope:set cp.bootstrapper powerdns_enabled true
cpcmd scope:set cp.bootstrapper powerdns_zone_type slave
cpcmd scope:set cp.bootstrapper powerdns_custom_config '["allow-notify-from":"1.2.3.4","slave":"yes","superslave":"yes"]'
cpcmd scope:set cp.bootstrapper powerdns_webserver_enable false
cpcmd scope:set cp.bootstrapper powerdns_nameservers '[ns1.domain.com,ns2.domain.com]'
cpcmd scope:set cp.bootstrapper powerdns_supermaster '[ip:1.2.3.4,nameserver:ns1.domain.com,account:master]'
cpcmd scope:set dns.default-provider powerdns
# Uncomment this if the slave nameserver is also providing hosting services
# cpcmd scope:set cp.bootstrapper powerdns_api_key 'abc1234'
# cpcmd scope:set cp.bootstrapper powerdns_api_uri 'https://ns1.domain.com/dns/api/v1'
upcp -sfb software/powerdns
Lastly, on the hosting nodes, assuming all DNS zone traffic is sent to the hidden master master.domain.com (IP address 1.2.3.3) with the API key from the hidden master located in /etc/pdns/pdns.conf
of abc1234
, configure each to use the same API key and endpoint discussed below.
cpcmd scope:set cp.bootstrapper powerdns_api_uri 'https://ns1.domain.com/dns/api/v1'
cpcmd scope:set cp.bootstrapper powerdns_nameservers '[ns1.domain.com,ns2.domain.com]'
cpcmd scope:set cp.bootstrapper powerdns_api_key 'abc1234'
cpcmd scope:set cp.bootstrapper powerdns_zone_type 'master'
cpcmd scope:set dns.default-provider powerdns
upcp -sfb software/powerdns
::: tip Notifying slaves
After making changes on the master, slaves previously unprovisioned must receive a NOTIFY command to create zones. pdns_control notify "*"
will send a NOTIFY command to all slaves for all domains on the master.
:::
PowerDNS is configured by default to use "127.0.0.1" for its NS records. In a working environment this is never the right option, but helps bridge learning. Bulk update can be used to clear all NS records on the apex, then provision NS records as set via powerdns_nameservers
.
Assuming your new DNS records are ns1.yourserver.com
and ns2.yourserver.com
, the following suffices:
First, to change the nameservers records used to provision new domains, use Bootstrapper.
cpcmd scope:set cp.bootstrapper powerdns_nameservers '[ns1.yourserver.com,ns2.yourserver.com]'
env BSARGS="--extra-vars=force=yes" upcp -sb software/powerdns
Next, to update NS records for existing domains.
include __DIR__ . '/lib/CLI/cmd.php';
$handler = new \Opcenter\Dns\Bulk();
// empty all NS records on the apex
// "_dummy_zone.com" has no effect, but used for completeness with the API
$handler->remove(new \Opcenter\Dns\Record('_dummy_zone.com', [
'name' => '',
'rr' => 'NS',
'parameter' => ''
]), function (\apnscpFunctionInterceptor $afi, \Opcenter\Dns\Record $r) {
return $afi->dns_get_provider() === 'powerdns';
});
$handler->add(new \Opcenter\Dns\Record('_dummy_zone.com', [
'name' => '',
'rr' => 'NS',
'parameter' => 'ns1.yourserver.com'
]), function (\apnscpFunctionInterceptor $afi, \Opcenter\Dns\Record $r) {
return $afi->dns_get_provider() === 'powerdns';
});
$handler->add(new \Opcenter\Dns\Record('_dummy_zone.com', [
'name' => '',
'rr' => 'NS',
'parameter' => 'ns2.yourserver.com'
]), function (\apnscpFunctionInterceptor $afi, \Opcenter\Dns\Record $r) {
return $afi->dns_get_provider() === 'powerdns';
});
Save the script in /usr/local/apnscp/update.php
and run env DEBUG=1 apnscp_php /usr/local/apnscp/update.php
to replace all NS records for domains that use PowerDNS.
New in 3.2.26
SOA controls several important replication attributes of a DNS zone. Changing primary nameservers or modifying zone defaults would necessitate updating SOA records. This can be done in a similar fashion using Opcenter\Dns\Bulk
. For convenience replace()
can accept a Record
object or closure, which will be called with the matching record. If a closure is used, the closure may return false
to skip modifying the record otherwise the second parameter, a Record
object, is used as replacement.
It is the responsibility of the closure to modify the Record
resource;
include __DIR__ . '/lib/CLI/cmd.php';
$handler = new \Opcenter\Dns\Bulk();
// PowerDNS doesn't let us delete the record; only replace() works
// "_dummy_zone.com" has no effect, but used for completeness with the API
$handler->replace(new \Opcenter\Dns\Record('_dummy_zone.com', [
'name' => '',
'rr' => 'SOA'
]), function (\apnscpFunctionInterceptor $afi, \Opcenter\Dns\Record $r) {
// update "rname" parameter
$r->setMeta('rname', 'ns1.foobar.com');
// update negative cache TTL
$r->setMeta('ttl', 300);
// return false to skip processing the record`
return $afi->dns_get_provider() === 'powerdns';
});
More examples are available in DNS.md.
Sometimes you may want to force a zone update - if changing public nameservers - or prune expired domains since AXFR-based clusters do not afford automated zone removals. These snippets come from hopefully.online:
pdns_control list-zones --type master | sed '$d' | xargs -L1 pdns_control notify
Zone removal should be run on slave servers. There is no need to run this on the master as zones are automatically removed upon deletion.
pdns_control list-zones --type slave | sed '$d' | xargs -I {} sh -c 'dig +short +tcp +norec @master.name.server SOA {} | grep -q ^ || pdnsutil delete-zone {}'
::: tip TCP mode
DNS uses UDP by default, which is a lossy protocol that does not guarantee
delivery. Enable TCP mode with -T
to guarantee the transmission was
received by master.domain.com.
:::
In the above example, only local requests may submit DNS modifications to the server. None of the below examples affect querying; DNS queries occur over 53/UDP typically (or 53/TCP if packet size exceeds UDP limits). Depending upon infrastructure, there are a few options to securely accept record submission, all of which require an API key for submission.
Apache's ProxyPass
directive send requests to the backend. Brute-force attempts are protected by mod_evasive bundled with ApisCP. Requests over this medium are protected by SSL, without HTTP/2 to ameliorate handshake overhead. In all but the very high volume API request environments, this will be acceptable.
In this situation, the endpoint is https://myserver.apiscp.com/dns. Changes are made to /etc/httpd/conf/httpd-custom.conf
within the <VirtualHost ... :443>
bracket (with SSLEngine On
!). After adding the below changes, systemctl restart httpd
.
<Location /dns>
ProxyPass http://127.0.0.1:8081
ProxyPassReverse http://127.0.0.1:8081
</Location>
Downsides: minor SSL overhead. Dependent upon Apache. Upsides: easy to setup. Protected by threat deterrence. PowerDNS accessible remotely via an easily controlled URI.
In the above example, API requests can be made via https://myserver.apiscp.com/dns, e.g.
curl -q -H 'X-API-Key: SOMEKEY' https://myserver.apiscp.com/dns/api/v1/servers/localhost
A separate webserver is available for real-time statistics through a UI. Additional authentication is required with webserver-password
as PowerDNS cannot see the connecting IP behind a proxy (cf. powerdns/pdns#10332).
cpcmd scope:set cp.bootstrapper powerdns_webserver_enable true
upcp -sb software/powerdns
Note: statistics may also be retrieved using pdns_control show "*"
As hinted above, placing PowerDNS behind Apache confers brute-force protection by mod_evasive. By default, 10 of the same requests in 2 seconds can trigger a brute-force block. Two solutions exist, either raise the same-page request threshold or disable mod_evasive.
Working off the example above <Location /dns> ...
<Location /dns>
# Raise threshold to 30 same-page requests in 2 seconds
DOSPageCount 60
DOSPageInterval 2
# Or disable entirely
DOSEnabled off
</Location>
429 Rate limit exceeded occurs whenever the page count exceeds the threshold. Raising DOSPageCount
/DOSPageInterval
will raise the threshold to trigger a 429 response. See also Evasive.md.
PowerDNS can also run by itself on a different port. In this situation, the network is configured to block all external requests to port 8081 except those whitelisted. For example, if the entire 32.12.1.1-32.12.1.255 network can be trusted and under your control, then whitelist the IP range:
cpcmd rampart:whitelist 32.12.1.1/24
Additionally, PowerDNS' whitelist must be updated as well. This can be quickly accomplished using the cp.bootstrapper Scope:
cpcmd scope:set cp.bootstrapper powerdns_localonly false
# Then re-run Bootstrapper
upcp -sb software/powerdns
Downsides: requires whitelisting IP addresses for access to API server. Must run on port different than Apache. Upsides: operates independently from Apache.
The server may be accessed once the source IP has been whitelisted,
curl -q -H 'X-API-Key: SOMEKEY' http://myserver.apiscp.com/api/v1/servers/localhost
Every server that runs ApisCP may delegate DNS authority to PowerDNS. This is ideal in distributed infrastructures in which coordination allows for seamless server-to-server migrations.
Taking the API key from above and using the SSL + Apache approach above, let's configure /usr/local/apnscp/config/auth.yaml
. Configuration within this file is secret and is not exposed via ApisCP's API. Once set restart ApisCP to compile configuration, systemctl restart apiscp
.
pdns:
# This url may be different if using running PowerDNS in standalone
uri: https://myserver.apiscp.com/dns/api/v1
key: your_api_key_here
type: native
# Optional SOA formatting, accepts "domain" format argument for current domain
soa: "hostmaster@%(domain)s"
ns:
- ns1.yourdomain.com
- ns2.yourdomain.com
recursion: false
## Optional additional nameservers
uri
value is the hostname of your master PowerDNS server running the HTTP API webserver (without a trailing slash)key
value is the API Key inpdns.conf
on the master nameserver.type
value defines domain type for replication. It's usually set tonative
when using DB replication, and tomaster
when using master-slave pdns replication (in such cluster the slaves should set this value toslave
, while superslaves will do it automatically when creating ingested zones).soa
value overrides default SOA contact format (hostmaster@DOMAIN). An optional format specifierdomain
replaces the format string with the current domain.ns
value is a list of nameservers as in the example above. Put nameservers on their own lines prefixed with a hyphen and indented accordingly. There is not currently a limit for the number of nameservers you may use, 2-5 is typical and should be geographically distributed per RFC 2182.recursion
controls ALIAS records, which are CNAMEs on apex (RFC 1034). Enabling requires configuration ofresolver
andexpand-alias
in pdns.conf.
PowerDNS may be configured as the default provider for all sites using the dns.default-provider
Scope. When adding a site in Nexus or AddDomain the key will be replaced with "DEFAULT". This is substituted automatically on account creation.
cpcmd scope:set dns.default-provider powerdns
Do not set dns.default-provider-key. API key is configured via
config/auth.yaml
.
- Module- overrides Dns_Module behavior
- Validator- service validator, checks input with AddDomain/EditDomain helpers
All module methods can be overwritten. The following are the bare minimum that are overwritten for this DNS provider to work:
atomicUpdate()
attempts a record modification, which must retain the original record if it failszoneAxfr()
returns all DNS recordsadd_record()
add a DNS recordremove_record()
removes a DNS recordget_hosting_nameservers()
returns nameservers for the DNS provideradd_zone_backend()
creates DNS zoneremove_zone_backend()
removes a DNS zone
See also: Creating a provider (hq.apiscp.com)
Submit a PR and have fun!