From: Charles-Henri Bruyand Date: Tue, 30 Oct 2018 16:33:39 +0000 (+0100) Subject: dnsdist: refactor webserver config in a single table X-Git-Tag: dnsdist-1.3.3~15^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=32c97b56fbd53d8737d9588dc23dae803eb23ffd;p=thirdparty%2Fpdns.git dnsdist: refactor webserver config in a single table --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 874ea4aac0..5f432d35f4 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -446,7 +446,7 @@ const std::vector 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)" }, diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index cae1df0181..b41573f4e1 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -149,7 +149,6 @@ static bool loadTLSCertificateAndKeys(shared_ptr& frontend, boost:: void setupLuaConfig(bool client) { typedef std::unordered_map >, 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 apiKey, const boost::optional > customHeaders) { + typedef std::unordered_map> > webserveropts_t; + + g_lua.writeFunction("setWebserverConfig", [](boost::optional vars) { setLuaSideEffect(); - setWebserverConfig(password, apiKey, customHeaders); + + if (!vars) { + return ; + } + if(vars->count("password")) { + const std::string password = boost::get(vars->at("password")); + + setWebserverPassword(password); + } + if(vars->count("apiKey")) { + // allows setting apiKey: nil to disable access with it + const std::string apiKey = boost::get(vars->at("apiKey")); + + setWebserverAPIKey(apiKey); + } + if(vars->count("customHeaders")) { + const boost::optional > headers = boost::get >(vars->at("customHeaders")); + + setWebserverCustomHeaders(headers); + } }); g_lua.writeFunction("controlSocket", [client](const std::string& str) { diff --git a/pdns/dnsdist-web.cc b/pdns/dnsdist-web.cc index f038b2cf90..a4ea665dd1 100644 --- a/pdns/dnsdist-web.cc +++ b/pdns/dnsdist-web.cc @@ -838,16 +838,29 @@ static void connectionThread(int sock, ComboAddress remote) close(sock); } } -void setWebserverConfig(const std::string& password, const boost::optional apiKey, const boost::optional > customHeaders) + +void setWebserverAPIKey(const boost::optional apiKey) { std::lock_guard 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 lock(g_webserverConfig.lock); + + g_webserverConfig.password = password; +} + +void setWebserverCustomHeaders(const boost::optional > customHeaders) +{ + std::lock_guard lock(g_webserverConfig.lock); + g_webserverConfig.customHeaders = customHeaders; } diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 8127eac2b8..b7ee81542c 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -993,7 +993,10 @@ struct WebserverConfig std::mutex lock; }; -void setWebserverConfig(const std::string& password, const boost::optional apiKey, const boost::optional > customHeaders); +void setWebserverAPIKey(const boost::optional apiKey); +void setWebserverPassword(const std::string& password); +void setWebserverCustomHeaders(const boost::optional > customHeaders); + void dnsdistWebserverThread(int sock, const ComboAddress& local); bool getMsgLen32(int fd, uint32_t* len); bool putMsgLen32(int fd, uint32_t len); diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index 86bd479f0b..88ee03321a 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -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 ~~~~~~~~~~~~~~~~~~~~ diff --git a/regression-tests.dnsdist/test_API.py b/regression-tests.dnsdist/test_API.py index 6ab44f651e..151cf7f737 100644 --- a/regression-tests.dnsdist/test_API.py +++ b/regression-tests.dnsdist/test_API.py @@ -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)