# Configuration options
There are two configuration parameters that can be used within the powerdns configuration file.
-## `experimental-dnsupdate`
-A setting to enable/disable DNS update support completely. The default is no, which means that DNS updates are ignored by PowerDNS (no message is logged about this!). Change the setting to **experimental-dnsupdate=yes** to enable DNS update support. Default is **no**.
+## `dnsupdate`
+A setting to enable/disable DNS update support completely. The default is no, which means that DNS updates are ignored by PowerDNS (no message is logged about this!). Change the setting to **dnsupdate=yes** to enable DNS update support. Default is **no**.
## `allow-dnsupdate-from`
A list of IP ranges that are allowed to perform updates on any domain. The default is 0.0.0.0/0, which means that all ranges are accepted. Multiple entries can be used on this line (**allow-dnsupdate-from=198.51.100.0/8 203.0.113.2/32**). The option can be left empty to disallow everything, this then should be used in combination with the **allow-dnsupdate-from** domainmetadata setting per zone.
Enabled DNS update (RFC2136) support functionality in PowerDNS by adding the following to the PowerDNS configuration file (pdns.conf).
```
-experimental-dnsupdate=yes
+dnsupdate=yes
allow-dnsupdate-from=
```
This tells PowerDNS to:
-1. Enable DNS update support([`experimental-dnsupdate`](settings.md#experimental-dnsupdate))
+1. Enable DNS update support([`dnsupdate`](settings.md#dnsupdate))
2. Allow updates from NO ip-address ([`allow-dnsupdate-from=`](settings.md#allow-dnsupdate-from))
We just told powerdns (via the configuration file) that we accept updates from nobody via the [`allow-dnsupdate-from`](settings.md#allow-dnsupdate-from) parameter. That's not very useful, so we're going to give permissions per zone, via the domainmetadata table.
1. The DNS update message is received. If it is TSIG signed, the TSIG is validated against the tsigkeys table. If it is not valid, Refused is returned to the requestor.
2. A check is performed on the zone to see if it is a valid zone. ServFail is returned when not valid.
-3. The **experimental-dnsupdate** setting is checked. Refused is returned when the setting is 'no'.
+3. The **dnsupdate** setting is checked. Refused is returned when the setting is 'no'.
4. If the **ALLOW-DNSUPDATE-FROM** has a value (from both domainmetadata and the configuration file), a check on the value is performed. If the requestor (sender of the update message) does not match the values in **ALLOW-DNSUPDATE-FROM**, Refused is returned.
5. If the message is TSIG signed, the TSIG keyname is compared with the TSIG keyname in domainmetadata. If they do not match, a Refused is send. The TSIG-ALLOW-DNSUPDATE domainmetadata setting is used to find which key belongs to the domain.
6. The backends are queried to find the backend for the given domain.
Answer questions for the ANY and RRSIG types on UDP with a truncated packet that
refers the remote server to TCP. Useful for mitigating reflection attacks.
+## `api-readonly`
+* Boolean
+* Default: no
+* Available since: 3.4
+
+Disallow data modification through the json API when set.
+
+## `api-key`
+* String
+* Available since: 3.4.1
+
+api-key REST API Static authentication key (required for API use)
+
## `cache-ttl`
* Integer
* Default: 20
Number of Distributor (backend) threads to start per receiver thread. See
["Authoritative Server Performance"](performance.md).
+## `dnsupdate`
+* Boolean
+* Default: no
+
+Enable/Disable DNS update (RFC2136) support.
+
## `do-ipv6-additional-processing`
* Boolean
* Default: yes
Entropy source file to use.
-## `experimental-api-readonly`
-* Boolean
-* Default: no
-* Available since: 3.4
-
-Disallow data modification through the json API when set.
-
-## `experimental-api-key`
-* String
-* Available since: 3.4.1
-
-experimental-api-key REST API Static authentication key (required for API use)
-
## `experimental-dname-processing`
* Boolean
* Default: no
Synthesise CNAME records from DNAME records as required. This approximately
doubles query load. **Do not combine with DNSSEC!**
-## `experimental-dnsupdate`
-* Boolean
-* Default: no
-
-Enable/Disable DNS update (RFC2136) support.
-
-## `experimental-json-interface`
+## `json-interface`
* Boolean
* Default: no
## Configuration option changes
### New options
-* [`experimental-api-key`](settings.md#experimental-api-key)
+* [`experimental-api-key`](settings.md#api-key)
* [`security-poll-suffix`](settings.md#security-poll-suffix)
# 3.3.1 to 3.4.0
Then configure as follows:
- experimental-json-interface=yes
- experimental-api-key=changeme
+ json-interface=yes
+ api-key=changeme
webserver=yes
Install PowerDNS Recursor, configured as follows:
- experimental-webserver=yes
- experimental-api-key=changeme
+ webserver=yes
+ api-key=changeme
auth-zones=
forward-zones=
forward-zones-recurse=
--------------
The PowerDNS daemons accept a static API Key, configured with the
-[`experimental-api-key`]('../authoritative/settings.md#experimental-api-key')
+[`api-key`]('../authoritative/settings.md#api-key')
option, which has to be sent in the `X-API-Key` header.
Note: Authoritative Server 3.4.0 and Recursor 3.6.0 and 3.6.1 use HTTP
Answer questions for the ANY type on UDP with a truncated packet that refers the
remote server to TCP. Useful for mitigating ANY reflection attacks.
+## `api-config-dir`
+* Path
+* Default: unset
+
+Directory where the REST API stores its configuration and zones.
+
+## `api-key`
+* String
+* Default: unset
+
+Static pre-shared authentication key for access to the REST API.
+
+## `api-readonly`
+* Boolean
+* Default: no
+
+Whether or not the JSON API is read only.
+
+## `api-logfile`
+* Path
+* Default: unset
+
+Location of the logs from the JSON parser.
+
## `auth-can-lower-ttl`
* Boolean
* Default: no
even if entropy is lacking. Change to `/dev/random` if PowerDNS should block
waiting for enough entropy to arrive.
-## `experimental-api-config-dir`
-* Path
-* Default: unset
-
-Directory where the REST API stores its configuration and zones.
-
-## `experimental-api-key`
-* String
-* Default: unset
-
-Static pre-shared authentication key for access to the REST API.
-
-## `experimental-api-readonly`
-* Boolean
-* Default: no
-
-Whether or not the JSON API is read only.
-
-## `experimental-logfile`
-* Path
-* Default: unset
-
-Location of the logs from the JSON parser.
-
-## `experimental-webserver`
+## `webserver`
* Boolean
* Default: no
-Start the experimental webserver for monitoring.
+Start the webserver for monitoring.
-## `experimental-webserver-address`
+## `webserver-address`
* IP Addresses, separated by spaces
* Default: 127.0.0.1
IP address for the webserver to listen on.
-## `experimental-webserver-password`
+## `webserver-allow-from`
+* IP addresses, comma separated
+* Default: 0.0.0.0, ::/0
+
+These subnets are allowed to access the webserver.
+
+## `webserver-password`
* String
* Default: unset
Password required to access the webserver.
-## `experimental-webserver-port`
+## `webserver-port`
* Integer
* Default: 8082
TCP port where the webserver should listen on.
-## `webserver-allow-from`
-* IP addresses, comma separated
-* Default: 0.0.0.0, ::/0
-
-These subnets are allowed to access the webserver.
-
## `etc-hosts-file`
* Path
* Default: /etc/hosts
void declareArguments()
{
::arg().set("local-port","The port on which we listen")="53";
- ::arg().setSwitch("experimental-dnsupdate","Enable/Disable DNS update (RFC2136) support. Default is no.")="no";
+ ::arg().setSwitch("dnsupdate","Enable/Disable DNS update (RFC2136) support. Default is no.")="no";
::arg().setSwitch("write-pid","Write a PID file")="yes";
::arg().set("allow-dnsupdate-from","A global setting to allow DNS updates from these IP ranges.")="127.0.0.0/8,::1";
::arg().setSwitch("forward-dnsupdate","A global setting to allow DNS update packages that are for a Slave domain, to be forwarded to the master.")="yes";
::arg().set("max-queue-length","Maximum queuelength before considering situation lost")="5000";
::arg().set("retrieval-threads", "Number of AXFR-retrieval threads for slave operation")="2";
- ::arg().setSwitch("experimental-json-interface", "If the webserver should serve JSON data")="no";
- ::arg().setSwitch("experimental-api-readonly", "If the JSON API should disallow data modification")="no";
- ::arg().set("experimental-api-key", "REST API Static authentication key (required for API use)")="";
+ ::arg().setSwitch("json-interface", "If the webserver should serve JSON data")="no";
+ ::arg().setSwitch("api-readonly", "If the JSON API should disallow data modification")="no";
+ ::arg().set("api-key", "REST API Static authentication key (required for API use)")="";
+ ::arg().set("api-logfile", "Filename of the log file for JSON parser" )= "/var/log/pdns.log";
::arg().setSwitch("experimental-dname-processing", "If we should support DNAME records")="no";
::arg().setCmd("help","Provide a helpful message");
::arg().set("max-tcp-connections","Maximum number of TCP connections")="10";
::arg().setSwitch("no-shuffle","Set this to prevent random shuffling of answers - for regression testing")="off";
- ::arg().set("experimental-logfile", "Filename of the log file for JSON parser" )= "/var/log/pdns.log";
::arg().set("setuid","If set, change user id to this uid for more security")="";
::arg().set("setgid","If set, change group id to this gid for more security")="";
t_fdm=getMultiplexer();
if(!t_id) {
- if(::arg().mustDo("experimental-webserver")) {
+ if(::arg().mustDo("webserver")) {
L<<Logger::Warning << "Enabling web server" << endl;
try {
new RecursorWebServer(t_fdm);
::arg().set("threads", "Launch this number of threads")="2";
::arg().set("processes", "Launch this number of processes (EXPERIMENTAL, DO NOT CHANGE)")="1";
::arg().set("config-name","Name of this virtual configuration - will rename the binary image")="";
- ::arg().set( "experimental-logfile", "Filename of the log file for JSON parser" )= "/var/log/pdns.log";
- ::arg().setSwitch("experimental-webserver", "Start a webserver for monitoring") = "no";
- ::arg().set("experimental-webserver-address", "IP Address of webserver to listen on") = "127.0.0.1";
- ::arg().set("experimental-webserver-port", "Port of webserver to listen on") = "8082";
- ::arg().set("experimental-webserver-password", "Password required for accessing the webserver") = "";
+ ::arg().set("api-config-dir", "Directory where REST API stores config and zones") = "";
+ ::arg().set("api-key", "REST API Static authentication key (required for API use)") = "";
+ ::arg().set("api-logfile", "Filename of the log file for JSON parser" )= "/var/log/pdns.log";
+ ::arg().set("api-readonly", "If the JSON API should disallow data modification") = "no";
+ ::arg().setSwitch("webserver", "Start a webserver for monitoring") = "no";
+ ::arg().set("webserver-address", "IP Address of webserver to listen on") = "127.0.0.1";
+ ::arg().set("webserver-port", "Port of webserver to listen on") = "8082";
+ ::arg().set("webserver-password", "Password required for accessing the webserver") = "";
::arg().set("webserver-allow-from","Webserver access is only allowed from these subnets")="0.0.0.0/0,::/0";
- ::arg().set("experimental-api-config-dir", "Directory where REST API stores config and zones") = "";
- ::arg().set("experimental-api-key", "REST API Static authentication key (required for API use)") = "";
::arg().set("carbon-ourname", "If set, overrides our reported hostname for carbon stats")="";
::arg().set("carbon-server", "If set, send metrics in carbon (graphite) format to this server")="";
::arg().set("carbon-interval", "Number of seconds between carbon (graphite) updates")="30";
- ::arg().set("experimental-api-readonly", "If the JSON API should disallow data modification") = "no";
::arg().set("quiet","Suppress logging of questions and answers")="";
::arg().set("logging-facility","Facility to log messages as. 0 corresponds to local0")="";
::arg().set("config-dir","Location of configuration directory (recursor.conf)")=SYSCONFDIR;
}
int PacketHandler::processUpdate(DNSPacket *p) {
- if (! ::arg().mustDo("experimental-dnsupdate"))
+ if (! ::arg().mustDo("dnsupdate"))
return RCode::Refused;
string msgPrefix="UPDATE (" + itoa(p->d.id) + ") from " + p->getRemote() + " for " + p->qdomain.toString() + ": ";
}
static void apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp) {
- const string& api_key = arg()["experimental-api-key"];
+ const string& api_key = arg()["api-key"];
if (optionsHandler(req, resp)) return;
throw HttpMethodNotAllowedException();
string prefix = " " + s_programname + "[";
- resp->body = logGrep(req->getvars["q"], ::arg()["experimental-logfile"], prefix);
+ resp->body = logGrep(req->getvars["q"], ::arg()["api-logfile"], prefix);
}
void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
UeberBackend B;
DNSSECKeeper dk;
- if (req->method == "POST" && !::arg().mustDo("experimental-api-readonly")) {
+ if (req->method == "POST" && !::arg().mustDo("api-readonly")) {
DomainInfo di;
Document document;
req->json(document);
static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
DNSName zonename = apiZoneIdToName(req->parameters["id"]);
- if(req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) {
+ if(req->method == "PUT" && !::arg().mustDo("api-readonly")) {
// update domain settings
UeberBackend B;
DomainInfo di;
fillZone(zonename, resp);
return;
}
- else if(req->method == "DELETE" && !::arg().mustDo("experimental-api-readonly")) {
+ else if(req->method == "DELETE" && !::arg().mustDo("api-readonly")) {
// delete domain
UeberBackend B;
DomainInfo di;
resp->body = "";
resp->status = 204; // No Content: declare that the zone is gone now
return;
- } else if (req->method == "PATCH" && !::arg().mustDo("experimental-api-readonly")) {
+ } else if (req->method == "PATCH" && !::arg().mustDo("api-readonly")) {
patchZone(req, resp);
return;
} else if (req->method == "GET") {
void AuthWebServer::webThread()
{
try {
- if(::arg().mustDo("experimental-json-interface")) {
+ if(::arg().mustDo("json-interface")) {
d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
d_ws->registerApiHandler("/servers/localhost/flush-cache", &apiServerFlushCache);
d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
static void apiWriteConfigFile(const string& filebasename, const string& content)
{
- if (::arg()["experimental-api-config-dir"].empty()) {
- throw ApiException("Config Option \"experimental-api-config-dir\" must be set");
+ if (::arg()["api-config-dir"].empty()) {
+ throw ApiException("Config Option \"api-config-dir\" must be set");
}
- string filename = ::arg()["experimental-api-config-dir"] + "/" + filebasename + ".conf";
+ string filename = ::arg()["api-config-dir"] + "/" + filebasename + ".conf";
ofstream ofconf(filename.c_str());
if (!ofconf) {
throw ApiException("Could not open config fragment file '"+filename+"' for writing: "+stringerror());
static void apiServerConfigAllowFrom(HttpRequest* req, HttpResponse* resp)
{
- if (req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) {
+ if (req->method == "PUT" && !::arg().mustDo("api-readonly")) {
Document document;
req->json(document);
const Value &jlist = document["value"];
static void doCreateZone(const Value& document)
{
- if (::arg()["experimental-api-config-dir"].empty()) {
- throw ApiException("Config Option \"experimental-api-config-dir\" must be set");
+ if (::arg()["api-config-dir"].empty()) {
+ throw ApiException("Config Option \"api-config-dir\" must be set");
}
if(stringFromJson(document, "name").empty())
throw ApiException("Single IP target '"+singleIPTarget+"' is invalid");
}
}
- string zonefilename = ::arg()["experimental-api-config-dir"] + "/" + confbasename + ".zone";
+ string zonefilename = ::arg()["api-config-dir"] + "/" + confbasename + ".zone";
ofstream ofzone(zonefilename.c_str());
if (!ofzone) {
throw ApiException("Could not open '"+zonefilename+"' for writing: "+stringerror());
static bool doDeleteZone(const DNSName& zonename)
{
- if (::arg()["experimental-api-config-dir"].empty()) {
- throw ApiException("Config Option \"experimental-api-config-dir\" must be set");
+ if (::arg()["api-config-dir"].empty()) {
+ throw ApiException("Config Option \"api-config-dir\" must be set");
}
string filename;
// this one must exist
- filename = ::arg()["experimental-api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".conf";
+ filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".conf";
if (unlink(filename.c_str()) != 0) {
return false;
}
// .zone file is optional
- filename = ::arg()["experimental-api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".zone";
+ filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".zone";
unlink(filename.c_str());
return true;
static void apiServerZones(HttpRequest* req, HttpResponse* resp)
{
- if (req->method == "POST" && !::arg().mustDo("experimental-api-readonly")) {
- if (::arg()["experimental-api-config-dir"].empty()) {
- throw ApiException("Config Option \"experimental-api-config-dir\" must be set");
+ if (req->method == "POST" && !::arg().mustDo("api-readonly")) {
+ if (::arg()["api-config-dir"].empty()) {
+ throw ApiException("Config Option \"api-config-dir\" must be set");
}
Document document;
if (iter == t_sstorage->domainmap->end())
throw ApiException("Could not find domain '"+zonename.toString()+"'");
- if(req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) {
+ if(req->method == "PUT" && !::arg().mustDo("api-readonly")) {
Document document;
req->json(document);
reloadAuthAndForwards();
fillZone(DNSName(stringFromJson(document, "name")), resp);
}
- else if(req->method == "DELETE" && !::arg().mustDo("experimental-api-readonly")) {
+ else if(req->method == "DELETE" && !::arg().mustDo("api-readonly")) {
if (!doDeleteZone(zonename)) {
throw ApiException("Deleting domain failed");
}
{
RecursorControlParser rcp; // inits
- d_ws = new AsyncWebServer(fdm, arg()["experimental-webserver-address"], arg().asNum("experimental-webserver-port"));
+ d_ws = new AsyncWebServer(fdm, arg()["webserver-address"], arg().asNum("webserver-port"));
d_ws->bind();
// legacy dispatch
auth-zones=
forward-zones=
forward-zones-recurse=
-experimental-api-config-dir=%(conf_dir)s
+api-config-dir=%(conf_dir)s
include-dir=%(conf_dir)s
"""
tf.seek(0, os.SEEK_SET) # rewind
subprocess.check_call(["sqlite3", SQLITE_DB], stdin=tf)
- pdnscmd = ("../pdns/pdns_server --daemon=no --local-port=5300 --socket-dir=./ --module-dir=../regression-tests/modules --no-shuffle --launch=gsqlite3 --gsqlite3-dnssec --send-root-referral --experimental-dnsupdate=yes --cache-ttl=0 --no-config --gsqlite3-dnssec=on --gsqlite3-database="+SQLITE_DB+" --experimental-json-interface=yes --webserver=yes --webserver-port="+WEBPORT+" --webserver-address=127.0.0.1 --webserver-password=something --experimental-api-key="+APIKEY).split()
+ pdnscmd = ("../pdns/pdns_server --daemon=no --local-port=5300 --socket-dir=./ --module-dir=../regression-tests/modules --no-shuffle --launch=gsqlite3 --gsqlite3-dnssec --send-root-referral --dnsupdate=yes --cache-ttl=0 --no-config --gsqlite3-dnssec=on --gsqlite3-database="+SQLITE_DB+" --json-interface=yes --webserver=yes --webserver-port="+WEBPORT+" --webserver-address=127.0.0.1 --webserver-password=something --api-key="+APIKEY).split()
else:
conf_dir = 'rec-conf.d'
with open(conf_dir+'/example.com..conf', 'w') as conf_file:
conf_file.write(REC_EXAMPLE_COM_CONF_TPL)
- pdnscmd = ("../pdns/pdns_recursor --daemon=no --socket-dir=. --config-dir=. --allow-from-file=acl.list --local-port=5555 --experimental-webserver=yes --experimental-webserver-port="+WEBPORT+" --experimental-webserver-address=127.0.0.1 --experimental-webserver-password=something --experimental-api-key="+APIKEY).split()
+ pdnscmd = ("../pdns/pdns_recursor --daemon=no --socket-dir=. --config-dir=. --allow-from-file=acl.list --local-port=5555 --webserver=yes --webserver-port="+WEBPORT+" --webserver-address=127.0.0.1 --webserver-password=something --api-key="+APIKEY).split()
# Now run pdns and the tests.
$RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --socket-dir=./ \
--no-shuffle --launch=bind --bind-config=./named-slave.conf --slave \
--send-root-referral --retrieval-threads=1 --config-name=bind-slave \
- --experimental-dnsupdate=yes \
+ --dnsupdate=yes \
--cache-ttl=$cachettl --no-config --experimental-dname-processing --bind-dnssec-db=./dnssec-slave.sqlite3 \
--module-dir=./modules &
echo 'waiting for zones to be loaded'
$RUNWRAPPER $PDNS --daemon=no --local-port=$port --config-dir=. \
--config-name=$backend --socket-dir=./ --no-shuffle \
--send-root-referral \
- --experimental-dnsupdate=yes \
+ --dnsupdate=yes \
--cache-ttl=$cachettl --experimental-dname-processing \
--disable-axfr-rectify=yes $lua_prequery &
--no-shuffle --launch=remote \
--cache-ttl=$cachettl --experimental-dname-processing --no-config \
--send-root-referral --distributor-threads=1 \
- --experimental-dnsupdate=yes \
+ --dnsupdate=yes \
--remote-connection-string="$connstr" $remote_add_param --module-dir=./modules &
;;
--no-shuffle --launch=tinydns \
--cache-ttl=$cachettl --experimental-dname-processing --no-config \
--send-root-referral \
- --experimental-dnsupdate=yes \
+ --dnsupdate=yes \
--tinydns-dbfile=../modules/tinydnsbackend/data.cdb --module-dir=./modules &
skipreasons="nodnssec noent nodyndns nometa noaxfr"
;;