]> granicus.if.org Git - pdns/commitdiff
dnsdist: Allow accessing the API with an optional API key
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 3 Mar 2016 17:35:01 +0000 (18:35 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 3 Mar 2016 17:35:01 +0000 (18:35 +0100)
The API key can be specified as an additional, optional parameter
to `webserver()`. If present in a X-API-Key header, it allows
access to the API URLs:
- /api/v1/servers/localhost
- /jsonstat

Others URLs are still only allowed through basic authentication.

pdns/README-dnsdist.md
pdns/dnsdist-lua.cc
pdns/dnsdist-web.cc
pdns/dnsdist.hh

index ce81d3b653712b16bbb18dc8f2541bc18de941bb..592db93ed997fc86f654ac9491b81bb416c97fa3 100644 (file)
@@ -880,7 +880,7 @@ Here are all functions:
  * Practical
     * `shutdown()`: shut down `dnsdist`
     * quit or ^D: exit the console
-    * `webserver(address, password)`: launch a webserver with stats on that address with that password
+    * `webserver(address, password [, apiKey])`: launch a webserver with stats on that address with that password
  * ACL related:
     * `addACL(netmask)`: add to the ACL set who can use this server
     * `setACL({netmask, netmask})`: replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us
index b7f5a6b998e009dfd7e2ae42eba4e2dd5f680c37..d13d5a1ce507d2d9146ef39eead10724905f8f1c 100644 (file)
@@ -995,7 +995,7 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
                        g_carbon.setState(ours);
                      });
 
-  g_lua.writeFunction("webserver", [client](const std::string& address, const std::string& password) {
+  g_lua.writeFunction("webserver", [client](const std::string& address, const std::string& password, const boost::optional<std::string> apiKey) {
       setLuaSideEffect();
       if(client)
        return;
@@ -1005,8 +1005,8 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
        SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
        SBind(sock, local);
        SListen(sock, 5);
-       auto launch=[sock, local, password]() {
-         thread t(dnsdistWebserverThread, sock, local, password);
+       auto launch=[sock, local, password, apiKey]() {
+         thread t(dnsdistWebserverThread, sock, local, password, apiKey ? *apiKey : "");
          t.detach();
        };
        if(g_launchWork) 
index 007d34238a07b7c2b66b0c7d81a77f8d279347dd..c1cdbc22adeb9c21743e9589889e25756522244a 100644 (file)
@@ -14,7 +14,7 @@
 #include "base64.hh"
 
 
-static bool compareAuthorization(YaHTTP::Request& req, const string &expected_password)
+static bool compareAuthorization(YaHTTP::Request& req, const string &expected_password, const string& expectedApiKey)
 {
   // validate password
   YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization");
@@ -31,6 +31,17 @@ static bool compareAuthorization(YaHTTP::Request& req, const string &expected_pa
     // this gets rid of terminating zeros
     auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), expected_password.c_str())));
   }
+  if (!auth_ok && !expectedApiKey.empty()) {
+    /* if this is a request for the API,
+       check if the API key is correct */
+    if (req.url.path=="/jsonstat" ||
+        req.url.path=="/api/v1/servers/localhost") {
+      header = req.headers.find("x-api-key");
+      if (header != req.headers.end()) {
+        auth_ok = (0==strcmp(header->second.c_str(), expectedApiKey.c_str()));
+      }
+    }
+  }
   return auth_ok;
 }
 
@@ -41,7 +52,7 @@ static void handleCORS(YaHTTP::Request& req, YaHTTP::Response& resp)
     if (req.method == "OPTIONS") {
       /* Pre-flight request */
       resp.headers["Access-Control-Allow-Methods"] = "GET";
-      resp.headers["Access-Control-Allow-Headers"] = "Authorization";
+      resp.headers["Access-Control-Allow-Headers"] = "Authorization, X-API-Key";
     }
 
     resp.headers["Access-Control-Allow-Origin"] = origin->second;
@@ -49,7 +60,7 @@ static void handleCORS(YaHTTP::Request& req, YaHTTP::Response& resp)
   }
 }
 
-static void connectionThread(int sock, ComboAddress remote, string password)
+static void connectionThread(int sock, ComboAddress remote, string password, string apiKey)
 {
   using namespace json11;
   vinfolog("Webserver handling connection from %s", remote.toStringWithPort());
@@ -81,13 +92,15 @@ static void connectionThread(int sock, ComboAddress remote, string password)
     resp.headers["X-Permitted-Cross-Domain-Policies"] = "none";
     resp.headers["X-XSS-Protection"] = "1; mode=block";
     resp.headers["Content-Security-Policy"] = "default-src 'self'; style-src 'self' 'unsafe-inline'";
+    /* no need to send back the API key if any */
+    resp.headers.erase("X-API-Key");
 
     if(req.method == "OPTIONS") {
       /* the OPTIONS method should not require auth, otherwise it breaks CORS */
       handleCORS(req, resp);
       resp.status=200;
     }
-    else if (!compareAuthorization(req, password)) {
+    else if (!compareAuthorization(req, password, apiKey)) {
       YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization");
       if (header != req.headers.end())
         errlog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, remote.toStringWithPort());
@@ -261,7 +274,7 @@ static void connectionThread(int sock, ComboAddress remote, string password)
        fclose(fp);
     }
 }
-void dnsdistWebserverThread(int sock, const ComboAddress& local, const std::string& password)
+void dnsdistWebserverThread(int sock, const ComboAddress& local, const std::string& password, const std::string& apiKey)
 {
   warnlog("Webserver launched on %s", local.toStringWithPort());
   for(;;) {
@@ -269,7 +282,7 @@ void dnsdistWebserverThread(int sock, const ComboAddress& local, const std::stri
       ComboAddress remote(local);
       int fd = SAccept(sock, remote);
       vinfolog("Got connection from %s", remote.toStringWithPort());
-      std::thread t(connectionThread, fd, remote, password);
+      std::thread t(connectionThread, fd, remote, password, apiKey);
       t.detach();
     }
     catch(std::exception& e) {
index d1c50f326e5a4ba216581b932f1df34adc89e5db..953f9044c3c78a5a2347b813af9e872adb62ec5d 100644 (file)
@@ -489,7 +489,7 @@ std::shared_ptr<DownstreamState> roundrobin(const NumberedServerVector& servers,
 int getEDNSZ(const char* packet, unsigned int len);
 void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent);
 uint16_t getEDNSOptionCode(const char * packet, size_t len);
-void dnsdistWebserverThread(int sock, const ComboAddress& local, const string& password);
+void dnsdistWebserverThread(int sock, const ComboAddress& local, const string& password, const string& apiKey);
 bool getMsgLen32(int fd, uint32_t* len);
 bool putMsgLen32(int fd, uint32_t len);
 void* tcpAcceptorThread(void* p);