]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
auth: add support for autoprimary management via API and pdnsutil
authorAki Tuomi <cmouse@cmouse.fi>
Wed, 21 Jul 2021 13:26:55 +0000 (16:26 +0300)
committerPeter van Dijk <peter.van.dijk@powerdns.com>
Mon, 10 Jan 2022 11:31:26 +0000 (12:31 +0100)
18 files changed:
docs/http-api/autoprimaries.rst [new file with mode: 0644]
docs/http-api/index.rst
docs/http-api/swagger/authoritative-api-swagger.yaml
docs/manpages/pdnsutil.1.rst
modules/bindbackend/bindbackend2.cc
modules/bindbackend/bindbackend2.hh
modules/gmysqlbackend/gmysqlbackend.cc
modules/godbcbackend/godbcbackend.cc
modules/gpgsqlbackend/gpgsqlbackend.cc
modules/gsqlite3backend/gsqlite3backend.cc
pdns/backends/gsql/gsqlbackend.cc
pdns/backends/gsql/gsqlbackend.hh
pdns/dnsbackend.hh
pdns/pdnsutil.cc
pdns/ueberbackend.cc
pdns/ueberbackend.hh
pdns/ws-api.cc
pdns/ws-auth.cc

diff --git a/docs/http-api/autoprimaries.rst b/docs/http-api/autoprimaries.rst
new file mode 100644 (file)
index 0000000..ab269d4
--- /dev/null
@@ -0,0 +1,71 @@
+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
index 60b449fff9243ad3287d16bb35eff979c8f1170a..256033f9f42cbb5acba4c8c7ecd7d3d43c69b67c 100644 (file)
@@ -367,6 +367,7 @@ The API exposes several endpoints and objects:
   cryptokey
   metadata
   tsigkey
+  autoprimaries
   search
   statistics
   cache
index cc05c1e49e3c515cc36808f6793326b2d7d09f4e..3761061858fda09af0d6abca802522f7934cf511 100644 (file)
@@ -864,6 +864,81 @@ paths:
           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
@@ -1071,6 +1146,20 @@ definitions:
         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:
index ca086d6112bcc9942bb832fe8790df26089b2b41..f6140955675de260ffc0a1c69dffc740a4a60cbc 100644 (file)
@@ -164,6 +164,10 @@ add-record *ZONE* *NAME* *TYPE* [*TTL*] *CONTENT*
     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*]..
index 2987ee5b7712e468d8f5f251d4bf5a861cd5f321..43e2034999fd08ea258a0724a6c7c32fe0934c26 100644 (file)
@@ -1333,6 +1333,31 @@ bool Bind2Backend::handle::get_list(DNSResourceRecord& r)
   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.
index db65ff266feec551a8df2bdea17cb62da5f9ee3e..ef5e2f0c25cadba398bc95d3099d40c609a17476 100644 (file)
@@ -236,7 +236,8 @@ public:
   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;
index 97c010ebddd1994b63e5d29e7eb72fa95aef0a72..fcea02024c6467ccb6545849d946da5c84cffaca 100644 (file)
@@ -107,6 +107,8 @@ public:
     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)");
 
index ee28a581b657a97f4fee64f0aeeced94aca0b4bf..87e7d4d828ab72a42326a3ad64932651c138e03d 100644 (file)
@@ -87,6 +87,8 @@ public:
     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)");
 
index 61563740221b3267f76b20aae68904eef22ec528..8bf8501d5ea25b9e2baa6df42fb1df4722b916ef 100644 (file)
@@ -114,6 +114,8 @@ public:
     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)");
 
index f0df57213e40249e66472207ed2441a9646c25cc..fd50ddcb601487735c777a18d1ddbdf6b3fc5fea 100644 (file)
@@ -100,6 +100,8 @@ public:
     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)");
 
index 4bb6e9c617566ed67ae9f223d378049d8feb20f0..39530fb22419b1036ec4e966ba8837cd7ed8d062 100644 (file)
@@ -71,6 +71,8 @@ GSQLBackend::GSQLBackend(const string &mode, const string &suffix)
   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");
@@ -141,6 +143,8 @@ GSQLBackend::GSQLBackend(const string &mode, const string &suffix)
   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;
@@ -1242,26 +1246,67 @@ skiprow:
   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
index 66fc76757909f5e844f1244b977aea2c389eaa90..365fd69a8a5e4b086b75eaad1213e7bbd4b238a6 100644 (file)
@@ -67,6 +67,8 @@ protected:
       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);
@@ -131,6 +133,8 @@ protected:
     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();
@@ -196,7 +200,9 @@ public:
   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;
@@ -289,6 +295,8 @@ private:
   string d_GetSuperMasterName;
   string d_GetSuperMasterIPs;
   string d_AddSuperMaster;
+  string d_RemoveAutoPrimaryQuery;
+  string d_ListAutoPrimariesQuery;
 
   string d_InsertZoneQuery;
   string d_InsertRecordQuery;
@@ -361,6 +369,8 @@ private:
   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;
index c63033d7484e6685e481bb70a9649bb826090484..84689c8087894132db7af4030c00dcb66bde79a4 100644 (file)
@@ -105,6 +105,17 @@ struct TSIGKey {
    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.
@@ -348,11 +359,23 @@ public:
   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)
   {
index 562780989f45798e2dfba1e4cbcf7439d5e15df4..0605bd387c65587711ea6ae85d03f3f9c91cc142 100644 (file)
@@ -1606,18 +1606,45 @@ static int addOrReplaceRecord(bool addOrReplace, const vector<string>& cmds) {
   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_)
 {
@@ -2323,6 +2350,8 @@ try
     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)
@@ -2878,6 +2907,16 @@ try
     }
     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;
index 5845bbf49bb3f6830be7403c528ad09b8f69efcd..b2559e717823e675f460fe93ef7c0b0a647d5cf1 100644 (file)
@@ -512,14 +512,30 @@ bool UeberBackend::getSOAUncached(const DNSName &domain, SOAData &sd)
   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)
index 53e1e8de63d3ab6160954bb08354f6763b2748f4..3ee73644276d827c72bdd15f91cdec1c97e4261c 100644 (file)
@@ -49,7 +49,9 @@ public:
 
   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 
index 470035386e9eafa642eab4dccb731ee37829164d..5c48fdae1a203935927905053d54904ed230edea 100644 (file)
@@ -97,7 +97,10 @@ static Json getServerDetail() {
     { "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
   };
 }
 
index 03ed5c3f1320711be14b8ee86f3fefa031f2fe46..9ed87e5645ef8082f95fbd69b7d50ada4c3c0d40 100644 (file)
@@ -1603,6 +1603,56 @@ static void apiServerTSIGKeyDetail(HttpRequest* req, HttpResponse* resp) {
   }
 }
 
+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);
@@ -2366,6 +2416,8 @@ void AuthWebServer::webThread()
       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);