--- /dev/null
+Autoprimaries
+=============
+
+This API is used to manage :ref:`autoprimaries <autoprimary-operation>`.
+
+Autoprimary endpoints
+---------------------
+
+.. openapi:: swagger/authoritative-api-swagger.yaml
+ :paths: /servers/{server_id}/autoprimaries /servers/{server_id}/autoprimaries/{ip}/{nameserver}
+
+Objects
+-------
+
+An autoprimary object represents a single autoprimary server.
+
+.. openapi:: swagger/authoritative-api-swagger.yaml
+ :definitions: Autoprimary
+
+Examples
+--------
+
+Listing autoprimaries
+^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+ GET /servers/localhost/autoprimaries HTTP/1.1
+ X-Api-Key: secret
+ Content-Type: application/json
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+
+ HTTP/1.1 200 Ok
+ Content-Type: application/json
+
+ [{"ip":"192.0.2.1","nameserver":"ns.example.com","account":""},{"ip":"192.0.2.50","nameserver":"ns.example.org","account":"example"}]
+
+Creating an autoprimary
+^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+ POST /servers/localhost/autoprimaries HTTP/1.1
+ X-Api-Key: secret
+ Content-Type: application/json
+
+ {"ip":"192.0.2.1","nameserver":"ns.example.com","account":""}
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+
+ HTTP/1.1 201 Created
+
+Deleting an autoprimary
+^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: http
+
+ DELETE /servers/localhost/autoprimaries/192.0.2.1/ns.example.com HTTP/1.1
+ X-Api-Key: secret
+ Content-Type: application/json
+
+Will yield a response similar to this (several headers omitted):
+
+.. code-block:: http
+
+ HTTP/1.1 204 No Content
cryptokey
metadata
tsigkey
+ autoprimaries
search
statistics
cache
description: 'OK, key was deleted'
<<: *commonErrors
+ '/servers/{server_id}/autoprimaries':
+ parameters:
+ - name: server_id
+ in: path
+ required: true
+ description: 'The id of the server to retrieve the key from'
+ type: string
+ get:
+ summary: 'Get a list of autoprimaries'
+ operationId: getAutoprimaries
+ tags:
+ - autoprimary
+ responses:
+ '200':
+ description: OK.
+ schema:
+ $ref: '#/definitions/Autoprimary'
+ <<: *commonErrors
+ post:
+ summary: 'Add an autoprimary'
+ description: 'This methods add a new autoprimary server.'
+ operationId: createAutoprimary
+ tags:
+ - autoprimary
+ parameters:
+ - name: ip
+ description: IP address for autoprimary server
+ required: true
+ in: body
+ schema:
+ $ref: '#/definitions/Autoprimary'
+ - name: nameserver
+ description: DNS name for autoprimary server
+ required: true
+ in: body
+ schema:
+ $ref: '#/definitions/Autoprimary'
+ - name: account
+ description: Optional account name
+ required: false
+ in: body
+ schema:
+ $ref: '#/definitions/Autoprimary'
+ responses:
+ '201':
+ description: Created
+ <<: *commonErrors
+
+ '/servers/{server_id}/autoprimaries/{ip}/{nameserver}':
+ parameters:
+ - name: server_id
+ in: path
+ required: true
+ description: 'The id of the server to retrieve to delete the autoprimary from'
+ type: string
+ - name: ip
+ in: path
+ required: true
+ description: 'IP address of autoprimary'
+ type: string
+ - name: nameserver
+ in: path
+ required: true
+ description: 'DNS name of the autoprimary'
+ type: string
+ delete:
+ summary: 'Delete the autoprimary entry'
+ operationId: deleteAutoprimary
+ tags:
+ - autoprimary
+ responses:
+ '204':
+ description: 'OK, key was deleted'
+ <<: *commonErrors
+
definitions:
Server:
title: Server
description: 'Set to "TSIGKey"'
readOnly: true
+ Autoprimary:
+ title: Autoprimary server
+ description: An autoprimary server that can provision new domains.
+ properties:
+ ip:
+ type: string
+ description: "IP address of the autoprimary server"
+ nameserver:
+ type: string
+ description: "DNS name of the autoprimary server"
+ account:
+ type: string
+ description: "Account name for the autoprimary server"
+
ConfigSetting:
title: ConfigSetting
properties:
and optional *TTL*. If *TTL* is not set, default will be used.
add-autoprimary *IP* *NAMESERVER* [*ACCOUNT*]
Add a autoprimary entry into the backend. This enables receiving zone updates from other servers.
+remove-autoprimary *IP* *NAMESERVER*
+ Remove an autoprimary from backend. Not supported by BIND backend.
+list-autoprimaries
+ List all autoprimaries.
create-zone *ZONE*
Create an empty zone named *ZONE*.
create-secondary-zone *ZONE* *PRIMARY* [*PRIMARY*]..
return false;
}
+bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
+{
+ if (getArg("supermaster-config").empty())
+ return false;
+
+ ifstream c_if(getArg("supermasters"), std::ios::in);
+ if (!c_if) {
+ g_log << Logger::Error << "Unable to open supermasters file for read: " << stringerror() << endl;
+ return false;
+ }
+
+ string line, sip, saccount;
+ while (getline(c_if, line)) {
+ std::istringstream ii(line);
+ ii >> sip;
+ if (sip.size() != 0) {
+ ii >> saccount;
+ primaries.emplace_back(sip, "", saccount);
+ }
+ }
+
+ c_if.close();
+ return true;
+}
+
bool Bind2Backend::superMasterBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** db)
{
// Check whether we have a configfile available.
void parseZoneFile(BB2DomainInfo* bbd);
void rediscover(string* status = nullptr) override;
- // for supermaster support
+ // for autoprimary support
+ bool autoPrimariesList(std::vector<AutoPrimary>& primaries) override;
bool superMasterBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** db) override;
static std::mutex s_supermaster_config_lock;
bool createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
declare(suffix, "supermaster-query", "", "select account from supermasters where ip=? and nameserver=?");
declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
+ declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = ? and nameserver = ?");
+ declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
declare(suffix, "insert-zone-query", "", "insert into domains (type,name,master,account,last_check,notified_serial) values(?,?,?,?,NULL,NULL)");
declare(suffix, "supermaster-query", "", "select account from supermasters where ip=? and nameserver=?");
declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values (?,?,?)");
+ declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = ? and nameserver = ?");
+ declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
declare(suffix, "insert-zone-query", "", "insert into domains (type,name,master,account,last_check,notified_serial) values(?,?,?,?,null,null)");
declare(suffix, "supermaster-query", "", "select account from supermasters where ip=$1 and nameserver=$2");
declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=$1 and account=$2");
declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values ($1,$2,$3)");
+ declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = $1 and nameserver = $2");
+ declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
declare(suffix, "insert-zone-query", "", "insert into domains (type,name,master,account,last_check, notified_serial) values($1,$2,$3,$4,null,null)");
declare(suffix, "supermaster-query", "", "select account from supermasters where ip=:ip and nameserver=:nameserver");
declare(suffix, "supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=:nameserver and account=:account");
declare(suffix, "supermaster-add", "", "insert into supermasters (ip, nameserver, account) values (:ip,:nameserver,:account)");
+ declare(suffix, "autoprimary-remove", "", "delete from supermasters where ip = :ip and nameserver = :nameserver");
+ declare(suffix, "list-autoprimaries", "", "select ip,nameserver,account from supermasters");
declare(suffix, "insert-zone-query", "", "insert into domains (type,name,master,account,last_check,notified_serial) values(:type, :domain, :masters, :account, null, null)");
d_SuperMasterInfoQuery=getArg("supermaster-query");
d_GetSuperMasterIPs=getArg("supermaster-name-to-ips");
d_AddSuperMaster=getArg("supermaster-add");
+ d_RemoveAutoPrimaryQuery=getArg("autoprimary-remove");
+ d_ListAutoPrimariesQuery=getArg("list-autoprimaries");
d_InsertZoneQuery=getArg("insert-zone-query");
d_InsertRecordQuery=getArg("insert-record-query");
d_UpdateMasterOfZoneQuery=getArg("update-master-query");
d_SuperMasterInfoQuery_stmt = nullptr;
d_GetSuperMasterIPs_stmt = nullptr;
d_AddSuperMaster_stmt = nullptr;
+ d_RemoveAutoPrimary_stmt = nullptr;
+ d_ListAutoPrimaries_stmt = nullptr;
d_InsertZoneQuery_stmt = nullptr;
d_InsertRecordQuery_stmt = nullptr;
d_InsertEmptyNonTerminalOrderQuery_stmt = nullptr;
return false;
}
-bool GSQLBackend::superMasterAdd(const string &ip, const string &nameserver, const string &account)
+bool GSQLBackend::superMasterAdd(const AutoPrimary& primary)
{
try{
reconnectIfNeeded();
d_AddSuperMaster_stmt ->
- bind("ip",ip)->
- bind("nameserver",nameserver)->
- bind("account",account)->
+ bind("ip",primary.ip)->
+ bind("nameserver",primary.nameserver)->
+ bind("account",primary.account)->
execute()->
reset();
}
catch (SSqlException &e){
- throw PDNSException("GSQLBackend unable to insert a supermaster with IP " + ip + " and nameserver name '" + nameserver + "' and account '" + account + "': " + e.txtReason());
+ throw PDNSException("GSQLBackend unable to insert an autoprimary with IP " + primary.ip + " and nameserver name '" + primary.nameserver + "' and account '" + primary.account + "': " + e.txtReason());
}
return true;
}
+bool GSQLBackend::autoPrimaryRemove(const AutoPrimary& primary)
+{
+ try{
+ reconnectIfNeeded();
+
+ d_RemoveAutoPrimary_stmt ->
+ bind("ip",primary.ip)->
+ bind("nameserver",primary.nameserver)->
+ execute()->
+ reset();
+
+ }
+ catch (SSqlException &e){
+ throw PDNSException("GSQLBackend unable to remove an autoprimary with IP " + primary.ip + " and nameserver name '" + primary.nameserver + "': " + e.txtReason());
+ }
+ return true;
+
+}
+
+bool GSQLBackend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
+{
+ try{
+ reconnectIfNeeded();
+
+ d_ListAutoPrimaries_stmt->
+ execute()->
+ getResult(d_result)->
+ reset();
+ }
+ catch (SSqlException &e){
+ throw PDNSException("GSQLBackend unable to list autoprimaries: " + e.txtReason());
+ }
+
+ for(const auto& row : d_result) {
+ ASSERT_ROW_COLUMNS("list-autoprimaries", row, 3);
+ primaries.emplace_back(row[0], row[1], row[2]);
+ }
+
+ return true;
+}
+
bool GSQLBackend::superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **ddb)
{
// check if we know the ip/ns couple in the database
d_SuperMasterInfoQuery_stmt = d_db->prepare(d_SuperMasterInfoQuery, 2);
d_GetSuperMasterIPs_stmt = d_db->prepare(d_GetSuperMasterIPs, 2);
d_AddSuperMaster_stmt = d_db->prepare(d_AddSuperMaster, 3);
+ d_RemoveAutoPrimary_stmt = d_db->prepare(d_RemoveAutoPrimaryQuery, 2);
+ d_ListAutoPrimaries_stmt = d_db->prepare(d_ListAutoPrimariesQuery, 0);
d_InsertZoneQuery_stmt = d_db->prepare(d_InsertZoneQuery, 4);
d_InsertRecordQuery_stmt = d_db->prepare(d_InsertRecordQuery, 9);
d_InsertEmptyNonTerminalOrderQuery_stmt = d_db->prepare(d_InsertEmptyNonTerminalOrderQuery, 4);
d_SuperMasterInfoQuery_stmt.reset();
d_GetSuperMasterIPs_stmt.reset();
d_AddSuperMaster_stmt.reset();
+ d_RemoveAutoPrimary_stmt.reset();
+ d_ListAutoPrimaries_stmt.reset();
d_InsertZoneQuery_stmt.reset();
d_InsertRecordQuery_stmt.reset();
d_InsertEmptyNonTerminalOrderQuery_stmt.reset();
bool createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& masters, const string& account) override;
bool createSlaveDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account) override;
bool deleteDomain(const DNSName &domain) override;
- bool superMasterAdd(const string &ip, const string &nameserver, const string &account) override;
+ bool superMasterAdd(const AutoPrimary& primary) override;
+ bool autoPrimaryRemove(const AutoPrimary& primary) override;
+ bool autoPrimariesList(std::vector<AutoPrimary>& primaries) override;
bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db) override;
void setStale(uint32_t domain_id) override;
void setFresh(uint32_t domain_id) override;
string d_GetSuperMasterName;
string d_GetSuperMasterIPs;
string d_AddSuperMaster;
+ string d_RemoveAutoPrimaryQuery;
+ string d_ListAutoPrimariesQuery;
string d_InsertZoneQuery;
string d_InsertRecordQuery;
unique_ptr<SSqlStatement> d_SuperMasterInfoQuery_stmt;
unique_ptr<SSqlStatement> d_GetSuperMasterIPs_stmt;
unique_ptr<SSqlStatement> d_AddSuperMaster_stmt;
+ unique_ptr<SSqlStatement> d_RemoveAutoPrimary_stmt;
+ unique_ptr<SSqlStatement> d_ListAutoPrimaries_stmt;
unique_ptr<SSqlStatement> d_InsertZoneQuery_stmt;
unique_ptr<SSqlStatement> d_InsertRecordQuery_stmt;
unique_ptr<SSqlStatement> d_InsertEmptyNonTerminalOrderQuery_stmt;
std::string key;
};
+struct AutoPrimary {
+ AutoPrimary(const string& new_ip, const string& new_nameserver, const string& new_account) {
+ this->ip = new_ip;
+ this->nameserver = new_nameserver;
+ this->account = new_account;
+ };
+ std::string ip;
+ std::string nameserver;
+ std::string account;
+};
+
class DNSPacket;
//! This virtual base class defines the interface for backends for PowerDNS.
void setArgPrefix(const string &prefix);
//! Add an entry for a super master
- virtual bool superMasterAdd(const string &ip, const string &nameserver, const string &account)
+ virtual bool superMasterAdd(const struct AutoPrimary& primary)
{
return false;
}
+ //! Remove an entry for a super master
+ virtual bool autoPrimaryRemove(const struct AutoPrimary& primary)
+ {
+ return false;
+ }
+
+ //! List all SuperMasters, returns false if feature not supported.
+ virtual bool autoPrimariesList(std::vector<AutoPrimary>& primaries)
+ {
+ return false;
+ }
+
//! determine if ip is a supermaster or a domain
virtual bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db)
{
return EXIT_SUCCESS;
}
-// addSuperMaster add anew super primary
+// addSuperMaster add a new autoprimary
static int addSuperMaster(const std::string &IP, const std::string &nameserver, const std::string &account)
{
UeberBackend B("default");
-
- if ( B.superMasterAdd(IP, nameserver, account) ){
+ const AutoPrimary primary(IP, nameserver, account);
+ if ( B.superMasterAdd(primary) ){
return EXIT_SUCCESS;
}
cerr<<"could not find a backend with autosecondary support"<<endl;
return EXIT_FAILURE;
}
+static int removeAutoPrimary(const std::string &IP, const std::string &nameserver)
+{
+ UeberBackend B("default");
+ const AutoPrimary primary(IP, nameserver, "");
+ if ( B.autoPrimaryRemove(primary) ){
+ return EXIT_SUCCESS;
+ }
+ cerr<<"could not find a backend with autosecondary support"<<endl;
+ return EXIT_FAILURE;
+}
+
+static int listAutoPrimaries()
+{
+ UeberBackend B("default");
+ vector<AutoPrimary> primaries;
+ if ( !B.autoPrimariesList(primaries) ){
+ cerr<<"could not find a backend with autosecondary support"<<endl;
+ return EXIT_FAILURE;
+ }
+
+ for(const auto& primary: primaries) {
+ cout<<"IP="<<primary.ip<<", NS="<<primary.nameserver<<", account="<<primary.account<<endl;
+ }
+
+ return EXIT_SUCCESS;
+}
+
// delete-rrset zone name type
static int deleteRRSet(const std::string& zone_, const std::string& name_, const std::string& type_)
{
cout<<" [content..] Add one or more records to ZONE"<<endl;
cout << "add-autoprimary IP NAMESERVER [account]" << endl;
cout << " Add a new autoprimary " << endl;
+ cout<<"remove-autoprimary IP NAMESERVER Remove an autoprimary" << endl;
+ cout<<"list-autoprimaries List all autoprimaries" << endl;
cout<<"add-zone-key ZONE {zsk|ksk} [BITS] [active|inactive] [published|unpublished]"<<endl;
cout<<" [rsasha1|rsasha1-nsec3-sha1|rsasha256|rsasha512|ecdsa256|ecdsa384";
#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBDECAF) || defined(HAVE_LIBCRYPTO_ED25519)
}
exit(addSuperMaster(cmds.at(1), cmds.at(2), cmds.size() > 3 ? cmds.at(3) : ""));
}
+ else if (cmds.at(0) == "remove-autoprimary") {
+ if(cmds.size() < 3) {
+ cerr << "Syntax: pdnsutil remove-autoprimary IP NAMESERVER" << endl;
+ return 0;
+ }
+ exit(removeAutoPrimary(cmds.at(1), cmds.at(2)));
+ }
+ else if (cmds.at(0) == "list-autoprimaries") {
+ exit(listAutoPrimaries());
+ }
else if (cmds.at(0) == "replace-rrset") {
if(cmds.size() < 5) {
cerr<<R"(Syntax: pdnsutil replace-rrset ZONE name type [ttl] "content" ["content"...])"<<endl;
return false;
}
-bool UeberBackend::superMasterAdd(const string &ip, const string &nameserver, const string &account)
+bool UeberBackend::superMasterAdd(const AutoPrimary &primary)
{
for(auto backend : backends)
- if(backend->superMasterAdd(ip, nameserver, account))
+ if(backend->superMasterAdd(primary))
return true;
return false;
}
+bool UeberBackend::autoPrimaryRemove(const AutoPrimary &primary)
+{
+ for(auto backend : backends)
+ if(backend->autoPrimaryRemove(primary))
+ return true;
+ return false;
+}
+
+bool UeberBackend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
+{
+ for(auto backend : backends)
+ if(backend->autoPrimariesList(primaries))
+ return true;
+ return false;
+}
+
bool UeberBackend::superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db)
{
for(auto backend : backends)
bool superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db);
- bool superMasterAdd(const string &ip, const string &nameserver, const string &account);
+ bool superMasterAdd(const AutoPrimary &primary);
+ bool autoPrimaryRemove(const struct AutoPrimary& primary);
+ bool autoPrimariesList(std::vector<AutoPrimary>& primaries);
/** Tracks all created UeberBackend instances for us. We use this vector to notify
existing threads of new modules
{ "daemon_type", productTypeApiType() },
{ "version", getPDNSVersion() },
{ "config_url", "/api/v1/servers/localhost/config{/config_setting}" },
- { "zones_url", "/api/v1/servers/localhost/zones{/zone}" }
+ { "zones_url", "/api/v1/servers/localhost/zones{/zone}" },
+#ifndef RECURSOR
+ { "autoprimaries_url", "/api/v1/servers/localhost/autoprimaries{/autoprimary}" }
+#endif
};
}
}
}
+static void apiServerAutoprimaryDetail(HttpRequest* req, HttpResponse* resp) {
+ UeberBackend B;
+ if (req->method == "DELETE") {
+ const AutoPrimary primary(req->parameters["ip"], req->parameters["nameserver"], "");
+ if (!B.autoPrimaryRemove(primary))
+ throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature");
+ resp->body = "";
+ resp->status = 204;
+ } else {
+ throw HttpMethodNotAllowedException();
+ }
+}
+
+static void apiServerAutoprimaries(HttpRequest* req, HttpResponse* resp) {
+ UeberBackend B;
+
+ if (req->method == "GET") {
+ std::vector<AutoPrimary> primaries;
+ if (!B.autoPrimariesList(primaries))
+ throw HttpInternalServerErrorException("Unable to retrieve autoprimaries");
+ Json::array doc;
+ for (const auto& primary: primaries) {
+ Json::object obj = {
+ { "ip", primary.ip },
+ { "nameserver", primary.nameserver },
+ { "account", primary.account }
+ };
+ doc.push_back(obj);
+ }
+ resp->setJsonBody(doc);
+ } else if (req->method == "POST") {
+ auto document = req->json();
+ AutoPrimary primary(stringFromJson(document, "ip"), stringFromJson(document, "nameserver"), "");
+
+ if (document["account"].is_string()) {
+ primary.account = document["account"].string_value();
+ }
+
+ if (primary.ip=="" or primary.nameserver=="") {
+ throw ApiException("ip and nameserver fields must be filled");
+ }
+ if (!B.superMasterAdd(primary))
+ throw HttpInternalServerErrorException("Cannot find backend with autoprimary feature");
+ resp->body = "";
+ resp->status = 201;
+ } else {
+ throw HttpMethodNotAllowedException();
+ }
+}
+
static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
UeberBackend B;
DNSSECKeeper dk(&B);
d_ws->registerApiHandler("/api/v1/servers/localhost/config", apiServerConfig);
d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", apiServerSearchData);
d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", apiServerStatistics);
+ d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries/<ip>/<nameserver>", &apiServerAutoprimaryDetail);
+ d_ws->registerApiHandler("/api/v1/servers/localhost/autoprimaries", &apiServerAutoprimaries);
d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", apiServerTSIGKeyDetail);
d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", apiServerTSIGKeys);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrieve", apiServerZoneAxfrRetrieve);