]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: refactor webserver config in a single table
authorCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Tue, 30 Oct 2018 16:33:39 +0000 (17:33 +0100)
committerCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Tue, 30 Oct 2018 16:33:39 +0000 (17:33 +0100)
pdns/dnsdist-console.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-web.cc
pdns/dnsdist.hh
pdns/dnsdistdist/docs/reference/config.rst
regression-tests.dnsdist/test_API.py

index 874ea4aac0b989b1489e7f458391d178cefefdf7..5f432d35f4f5bb0d256b3b2566a0a9a2103d36ef 100644 (file)
@@ -446,7 +446,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "setUDPMultipleMessagesVectorSize", true, "n", "set the size of the vector passed to recvmmsg() to receive UDP messages. Default to 1 which means that the feature is disabled and recvmsg() is used instead" },
   { "setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds" },
   { "setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged" },
-  { "setWebserverConfig", true, "password [, apiKey [, customHeaders ]]", "Updates webserver configuration" },
+  { "setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders}]", "Updates webserver configuration" },
   { "show", true, "string", "outputs `string`" },
   { "showACL", true, "", "show our ACL set" },
   { "showBinds", true, "", "show listening addresses (frontends)" },
index cae1df01812c1c933c1dcbcb3bdfd2de614a5ca0..b41573f4e18c51a4e093ac897af9f921d84e44b4 100644 (file)
@@ -149,7 +149,6 @@ static bool loadTLSCertificateAndKeys(shared_ptr<TLSFrontend>& frontend, boost::
 void setupLuaConfig(bool client)
 {
   typedef std::unordered_map<std::string, boost::variant<bool, std::string, vector<pair<int, std::string> >, DownstreamState::checkfunc_t > > newserver_t;
-
   g_lua.writeFunction("inClientStartup", [client]() {
         return client && !g_configurationDone;
   });
@@ -631,7 +630,9 @@ void setupLuaConfig(bool client)
        SBind(sock, local);
        SListen(sock, 5);
        auto launch=[sock, local, password, apiKey, customHeaders]() {
-          setWebserverConfig(password, apiKey, customHeaders);
+          setWebserverPassword(password);
+          setWebserverAPIKey(apiKey);
+          setWebserverCustomHeaders(customHeaders);
           thread t(dnsdistWebserverThread, sock, local);
          t.detach();
        };
@@ -647,9 +648,30 @@ void setupLuaConfig(bool client)
 
     });
 
-  g_lua.writeFunction("setWebserverConfig", [](const std::string& password, const boost::optional<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > customHeaders) {
+  typedef std::unordered_map<std::string, boost::variant<std::string, std::map<std::string, std::string>> > webserveropts_t;
+
+  g_lua.writeFunction("setWebserverConfig", [](boost::optional<webserveropts_t> vars) {
       setLuaSideEffect();
-      setWebserverConfig(password, apiKey, customHeaders);
+
+      if (!vars) {
+        return ;
+      }
+      if(vars->count("password")) {
+        const std::string password = boost::get<std::string>(vars->at("password"));
+
+        setWebserverPassword(password);
+      }
+      if(vars->count("apiKey")) {
+        // allows setting apiKey: nil to disable access with it
+        const std::string apiKey = boost::get<std::string>(vars->at("apiKey"));
+
+        setWebserverAPIKey(apiKey);
+      }
+      if(vars->count("customHeaders")) {
+        const boost::optional<std::map<std::string, std::string> > headers = boost::get<std::map<std::string, std::string> >(vars->at("customHeaders"));
+
+        setWebserverCustomHeaders(headers);
+      }
     });
 
   g_lua.writeFunction("controlSocket", [client](const std::string& str) {
index f038b2cf90613ecc450e193155225461c6f6a1cf..a4ea665dd1bf54e42681aa3bcd1cc12e87258f60 100644 (file)
@@ -838,16 +838,29 @@ static void connectionThread(int sock, ComboAddress remote)
     close(sock);
   }
 }
-void setWebserverConfig(const std::string& password, const boost::optional<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > customHeaders)
+
+void setWebserverAPIKey(const boost::optional<std::string> apiKey)
 {
   std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
 
-  g_webserverConfig.password = password;
   if (apiKey) {
     g_webserverConfig.apiKey = *apiKey;
   } else {
     g_webserverConfig.apiKey.clear();
   }
+}
+
+void setWebserverPassword(const std::string& password)
+{
+  std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
+
+  g_webserverConfig.password = password;
+}
+
+void setWebserverCustomHeaders(const boost::optional<std::map<std::string, std::string> > customHeaders)
+{
+  std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
+
   g_webserverConfig.customHeaders = customHeaders;
 }
 
index 8127eac2b87adacfdd29005dee3138463090d37e..b7ee81542c69e8910ee4c5442a9e80a22ef474c5 100644 (file)
@@ -993,7 +993,10 @@ struct WebserverConfig
   std::mutex lock;
 };
 
-void setWebserverConfig(const std::string& password, const boost::optional<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > customHeaders);
+void setWebserverAPIKey(const boost::optional<std::string> apiKey);
+void setWebserverPassword(const std::string& password);
+void setWebserverCustomHeaders(const boost::optional<std::map<std::string, std::string> > customHeaders);
+
 void dnsdistWebserverThread(int sock, const ComboAddress& local);
 bool getMsgLen32(int fd, uint32_t* len);
 bool putMsgLen32(int fd, uint32_t len);
index 86bd479f0bb5a4e40eea257bc62ba9990dfd86bc..88ee03321aefa581237d50708d98a28d0f78dcdb 100644 (file)
@@ -232,15 +232,19 @@ Webserver configuration
   :param bool allow: Set to true to allow modification through the API
   :param str dir: A valid directory where the configuration files will be written by the API.
 
-.. function:: setWebserverConfig(password[, apikey[, custom_headers]])
+.. function:: setWebserverConfig(options)
 
   .. versionadded:: 1.3.3
 
   Setup webserver configuration. See :func:`webserver`.
 
-  :param str password: The password required to access the webserver
-  :param str apikey: The key required to access the API
-  :param {[str]=str,...} custom_headers: Allows setting custom headers and removing the defaults
+  :param table options: A table with key: value pairs with webserver options.
+
+  Options:
+
+  * ``password=newPassword``: string - Changes the API password
+  * ``apikey=newKey``: string - Changes the API Key (set to an empty string do disable it)
+  * ``custom_headers={[str]=str,...}``: map of string - Allows setting custom headers and removing the defaults.
                  
 Access Control Lists
 ~~~~~~~~~~~~~~~~~~~~
index 6ab44f651e41a240ea5289d0b0778e4e0e7cf016..151cf7f737e6f2e4bbbe188fb790dc80214dc75b 100644 (file)
@@ -383,6 +383,54 @@ class TestAPIWritable(DNSDistTest):
 setACL({"192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"})
 """)
 
+class TestAPICustomHeaders(DNSDistTest):
+
+    _webTimeout = 2.0
+    _webServerPort = 8083
+    _webServerBasicAuthPassword = 'secret'
+    _webServerAPIKey = 'apisecret'
+    # paths accessible using the API key only
+    _apiOnlyPath = '/api/v1/servers/localhost/config'
+    # paths accessible using basic auth only (list not exhaustive)
+    _basicOnlyPath = '/'
+    _consoleKey = DNSDistTest.generateConsoleKey()
+    _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+    _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
+    _config_template = """
+    setKey("%s")
+    controlSocket("127.0.0.1:%s")
+    setACL({"127.0.0.1/32", "::1/128"})
+    newServer({address="127.0.0.1:%s"})
+    webserver("127.0.0.1:%s", "%s", "%s", {["X-Frame-Options"]="", ["X-Custom"]="custom"})
+    """
+
+    def testBasicHeaders(self):
+        """
+        API: Basic custom headers
+        """
+
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + self._basicOnlyPath
+
+        r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEquals(r.status_code, 200)
+        self.assertEquals(r.headers.get('x-custom'), "custom")
+        self.assertFalse("x-frame-options" in r.headers)
+
+    def testBasicHeadersUpdate(self):
+        """
+        API: Basic update of custom headers
+        """
+
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + self._basicOnlyPath
+        self.sendConsoleCommand('setWebserverConfig({customHeaders={["x-powered-by"]="dnsdist"}})')
+        r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
+        self.assertTrue(r)
+        self.assertEquals(r.status_code, 200)
+        self.assertEquals(r.headers.get('x-powered-by'), "dnsdist")
+        self.assertTrue("x-frame-options" in r.headers)
+
+
 class TestAPIAuth(DNSDistTest):
 
     _webTimeout = 2.0
@@ -411,15 +459,14 @@ class TestAPIAuth(DNSDistTest):
         API: Basic Authentication updating credentials
         """
 
-        self.sendConsoleCommand('setWebserverConfig("{}")'.format(self._webServerBasicAuthPasswordNew))
-
         url = 'http://127.0.0.1:' + str(self._webServerPort) + self._basicOnlyPath
+        self.sendConsoleCommand('setWebserverConfig({{password="{}"}})'.format(self._webServerBasicAuthPasswordNew))
+
         r = requests.get(url, auth=('whatever', self._webServerBasicAuthPasswordNew), timeout=self._webTimeout)
         self.assertTrue(r)
         self.assertEquals(r.status_code, 200)
 
         # Make sure the old password is not usable any more
-        url = 'http://127.0.0.1:' + str(self._webServerPort) + self._basicOnlyPath
         r = requests.get(url, auth=('whatever', self._webServerBasicAuthPassword), timeout=self._webTimeout)
         self.assertEquals(r.status_code, 401)
 
@@ -428,17 +475,16 @@ class TestAPIAuth(DNSDistTest):
         API: X-Api-Key updating credentials
         """
 
-        self.sendConsoleCommand('setWebserverConfig("{}", "{}")'.format(self._webServerBasicAuthPasswordNew, self._webServerAPIKeyNew))
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + self._apiOnlyPath
+        self.sendConsoleCommand('setWebserverConfig({{apiKey="{}"}})'.format(self._webServerAPIKeyNew))
 
         headers = {'x-api-key': self._webServerAPIKeyNew}
-        url = 'http://127.0.0.1:' + str(self._webServerPort) + self._apiOnlyPath
         r = requests.get(url, headers=headers, timeout=self._webTimeout)
         self.assertTrue(r)
         self.assertEquals(r.status_code, 200)
 
         # Make sure the old password is not usable any more
         headers = {'x-api-key': self._webServerAPIKey}
-        url = 'http://127.0.0.1:' + str(self._webServerPort) + self._apiOnlyPath
         r = requests.get(url, headers=headers, timeout=self._webTimeout)
         self.assertEquals(r.status_code, 401)
 
@@ -447,18 +493,16 @@ class TestAPIAuth(DNSDistTest):
         API: X-Api-Key updated to none (disabled)
         """
 
-        self.sendConsoleCommand('setWebserverConfig("{}", "{}")'.format(self._webServerBasicAuthPasswordNew, self._webServerAPIKeyNew))
+        url = 'http://127.0.0.1:' + str(self._webServerPort) + self._apiOnlyPath
+        self.sendConsoleCommand('setWebserverConfig({{apiKey="{}"}})'.format(self._webServerAPIKeyNew))
 
         headers = {'x-api-key': self._webServerAPIKeyNew}
-        url = 'http://127.0.0.1:' + str(self._webServerPort) + self._apiOnlyPath
         r = requests.get(url, headers=headers, timeout=self._webTimeout)
         self.assertTrue(r)
         self.assertEquals(r.status_code, 200)
 
         # now disable apiKey
-        self.sendConsoleCommand('setWebserverConfig("{}")'.format(self._webServerBasicAuthPasswordNew))
+        self.sendConsoleCommand('setWebserverConfig({apiKey=""})')
 
-        headers = {'x-api-key': self._webServerAPIKeyNew}
-        url = 'http://127.0.0.1:' + str(self._webServerPort) + self._apiOnlyPath
         r = requests.get(url, headers=headers, timeout=self._webTimeout)
         self.assertEquals(r.status_code, 401)