From 4f3bd690b11961ce2cba380f1f8e966e9713c6b0 Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Tue, 11 Jun 2024 12:41:24 +0200 Subject: [PATCH] dnsdist: Switch Webserver and console to the new configuration --- pdns/dnsdistdist/dnsdist-configuration.hh | 24 +- pdns/dnsdistdist/dnsdist-console.cc | 16 +- pdns/dnsdistdist/dnsdist-console.hh | 2 +- pdns/dnsdistdist/dnsdist-lua-web.cc | 5 +- pdns/dnsdistdist/dnsdist-lua.cc | 169 +++++++------- pdns/dnsdistdist/dnsdist-lua.hh | 2 +- pdns/dnsdistdist/dnsdist-metrics.cc | 4 +- pdns/dnsdistdist/dnsdist-web.cc | 268 +++++++++------------- pdns/dnsdistdist/dnsdist-web.hh | 20 +- pdns/dnsdistdist/dnsdist.cc | 61 ++++- pdns/dnsdistdist/test-dnsdist-lua-ffi.cc | 2 +- 11 files changed, 273 insertions(+), 300 deletions(-) diff --git a/pdns/dnsdistdist/dnsdist-configuration.hh b/pdns/dnsdistdist/dnsdist-configuration.hh index 9889d40b9a94..91a41f208232 100644 --- a/pdns/dnsdistdist/dnsdist-configuration.hh +++ b/pdns/dnsdistdist/dnsdist-configuration.hh @@ -23,8 +23,11 @@ #include #include +#include +#include #include +#include "credentials.hh" #include "dnsdist-query-count.hh" #include "dnsdist-rule-chains.hh" #include "iputils.hh" @@ -156,8 +159,6 @@ struct Configuration { std::set d_capabilitiesToRetain; std::vector d_tcpFastOpenKey; - ComboAddress d_consoleServerAddress{"127.0.0.1:5199"}; - std::string d_consoleKey; #ifdef __linux__ // On Linux this gives us 128k pending queries (default is 8192 queries), // which should be enough to deal with huge spikes @@ -193,23 +194,21 @@ struct Configuration a RCU-like mechanism */ struct RuntimeConfiguration { - // ca tient pas la route: meilleure option: stocker un type plus opaque dans la configuration (dnsdist::rules::RuleChains) et - // laisser le soin a dnsdist::rules de le gerer - /* std::vector d_cacheMissRuleActions; - std::vector d_respruleactions; - std::vector d_cachehitrespruleactions; - std::vector d_selfansweredrespruleactions; - std::vector d_cacheInsertedRespRuleActions; - std::vector d_XFRRespRuleActions; - */ rules::RuleChains d_ruleChains; servers_t d_backends; std::map> d_pools; + std::shared_ptr d_webPassword; + std::shared_ptr d_webAPIKey; + std::optional> d_webCustomHeaders; std::shared_ptr d_lbPolicy; NetmaskGroup d_ACL; NetmaskGroup d_proxyProtocolACL; NetmaskGroup d_consoleACL; + NetmaskGroup d_webServerACL; + std::optional d_webServerAddress{std::nullopt}; dnsdist::QueryCount::Configuration d_queryCountConfig; + ComboAddress d_consoleServerAddress{"127.0.0.1:5199"}; + std::string d_consoleKey; std::string d_secPollSuffix{"secpoll.powerdns.com."}; std::string d_apiConfigDirectory; uint64_t d_dynBlocksPurgeInterval{60}; @@ -231,6 +230,9 @@ struct RuntimeConfiguration uint16_t d_tlsSessionCacheSessionValidity{600}; uint16_t d_tlsSessionCacheMaxSessionsPerBackend{20}; DNSAction::Action d_dynBlockAction{DNSAction::Action::Drop}; + bool d_apiRequiresAuthentication{true}; + bool d_dashboardRequiresAuthentication{true}; + bool d_statsRequireAuthentication{true}; bool d_truncateTC{false}; bool d_fixupCase{false}; bool d_queryCountEnabled{false}; diff --git a/pdns/dnsdistdist/dnsdist-console.cc b/pdns/dnsdistdist/dnsdist-console.cc index d4b1b7b552ec..f5ed5c705f20 100644 --- a/pdns/dnsdistdist/dnsdist-console.cc +++ b/pdns/dnsdistdist/dnsdist-console.cc @@ -178,7 +178,7 @@ static bool putMsgLen32(int fileDesc, uint32_t len) static ConsoleCommandResult sendMessageToServer(int fileDesc, const std::string& line, dnsdist::crypto::authenticated::Nonce& readingNonce, dnsdist::crypto::authenticated::Nonce& writingNonce, const bool outputEmptyLine) { - const auto& consoleKey = dnsdist::configuration::getImmutableConfiguration().d_consoleKey; + const auto& consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey; string msg = dnsdist::crypto::authenticated::encryptSym(line, consoleKey, writingNonce); const auto msgLen = msg.length(); if (msgLen > std::numeric_limits::max()) { @@ -225,8 +225,8 @@ namespace dnsdist::console { void doClient(const std::string& command) { - const auto consoleKey = dnsdist::configuration::getImmutableConfiguration().d_consoleKey; - const auto server = dnsdist::configuration::getImmutableConfiguration().d_consoleServerAddress; + const auto consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey; + const auto server = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleServerAddress; if (!dnsdist::crypto::authenticated::isValidKey(consoleKey)) { cerr << "The currently configured console key is not valid, please configure a valid key using the setKey() directive" << endl; return; @@ -932,7 +932,7 @@ static void controlClientThread(ConsoleConnection&& conn) setTCPNoDelay(conn.getFD()); - const auto& consoleKey = dnsdist::configuration::getImmutableConfiguration().d_consoleKey; + const auto consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey; dnsdist::crypto::authenticated::Nonce theirs; dnsdist::crypto::authenticated::Nonce ours; dnsdist::crypto::authenticated::Nonce readingNonce; @@ -1065,11 +1065,11 @@ static void controlClientThread(ConsoleConnection&& conn) } } -// NOLINTNEXTLINE(performance-unnecessary-value-param): this is thread -void controlThread(std::shared_ptr&& acceptFD, ComboAddress local) +void controlThread(Socket&& acceptFD) { try { setThreadName("dnsdist/control"); + const ComboAddress local = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleServerAddress; s_connManager.setMaxConcurrentConnections(dnsdist::configuration::getImmutableConfiguration().d_consoleMaxConcurrentConnections); ComboAddress client; @@ -1081,8 +1081,8 @@ void controlThread(std::shared_ptr&& acceptFD, ComboAddress local) int sock{-1}; infolog("Accepting control connections on %s", local.toStringWithPort()); - while ((sock = SAccept(acceptFD->getHandle(), client)) >= 0) { - const auto& consoleKey = dnsdist::configuration::getImmutableConfiguration().d_consoleKey; + while ((sock = SAccept(acceptFD.getHandle(), client)) >= 0) { + const auto& consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey; FDWrapper socket(sock); if (!dnsdist::crypto::authenticated::isValidKey(consoleKey)) { vinfolog("Control connection from %s dropped because we don't have a valid key configured, please configure one using setKey()", client.toStringWithPort()); diff --git a/pdns/dnsdistdist/dnsdist-console.hh b/pdns/dnsdistdist/dnsdist-console.hh index e59e58b99d74..ef27bc0a1be4 100644 --- a/pdns/dnsdistdist/dnsdist-console.hh +++ b/pdns/dnsdistdist/dnsdist-console.hh @@ -32,7 +32,7 @@ namespace dnsdist::console const std::vector>& getConfigurationDelta(); void doClient(const std::string& command); void doConsole(); -void controlThread(std::shared_ptr&& acceptFD, ComboAddress local); +void controlThread(Socket&& acceptFD); void clearHistory(); #ifndef DISABLE_COMPLETION diff --git a/pdns/dnsdistdist/dnsdist-lua-web.cc b/pdns/dnsdistdist/dnsdist-lua-web.cc index 35cf0d0f0963..cd7e47f91ed7 100644 --- a/pdns/dnsdistdist/dnsdist-lua-web.cc +++ b/pdns/dnsdistdist/dnsdist-lua-web.cc @@ -25,14 +25,17 @@ #include "dnsdist-lua.hh" #include "dnsdist-web.hh" +namespace dnsdist::webserver +{ void registerWebHandler(const std::string& endpoint, std::function handler, bool isLua); +} void setupLuaWeb(LuaContext& luaCtx) { #ifndef DISABLE_LUA_WEB_HANDLERS luaCtx.writeFunction("registerWebHandler", [](const std::string& path, std::function handler) { /* LuaWrapper does a copy for objects passed by reference, so we pass a pointer */ - registerWebHandler(path, [handler](const YaHTTP::Request& req, YaHTTP::Response& resp) { handler(&req, &resp); }, true); + dnsdist::webserver::registerWebHandler(path, [handler](const YaHTTP::Request& req, YaHTTP::Response& resp) { handler(&req, &resp); }, true); }); luaCtx.registerMember("path", [](const YaHTTP::Request& req) -> std::string { return req.url.path; }, [](YaHTTP::Request& req, const std::string& path) { (void) path; }); diff --git a/pdns/dnsdistdist/dnsdist-lua.cc b/pdns/dnsdistdist/dnsdist-lua.cc index 4b1d75d7800c..52a1b44d828c 100644 --- a/pdns/dnsdistdist/dnsdist-lua.cc +++ b/pdns/dnsdistdist/dnsdist-lua.cc @@ -82,7 +82,7 @@ using std::thread; -static boost::optional>> g_launchWork = boost::none; +static std::optional>> s_launchWork{std::nullopt}; static boost::tribool s_noLuaSideEffect; @@ -699,8 +699,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } if (ret->connected) { - if (g_launchWork) { - g_launchWork->push_back([ret]() { + if (s_launchWork) { + s_launchWork->push_back([ret]() { ret->start(); }); } @@ -1290,29 +1290,26 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) return; } - try { - int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0); - SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1); - SBind(sock, local); - SListen(sock, 5); - auto launch = [sock, local]() { - thread thr(dnsdistWebserverThread, sock, local); + dnsdist::configuration::updateRuntimeConfiguration([local](dnsdist::configuration::RuntimeConfiguration& config) { + config.d_webServerAddress = local; + }); + + if (dnsdist::configuration::isConfigurationDone()) { + try { + auto sock = Socket(local.sin4.sin_family, SOCK_STREAM, 0); + sock.bind(local, true); + sock.listen(5); + thread thr(dnsdist::webserver::WebserverThread, std::move(sock)); thr.detach(); - }; - if (g_launchWork) { - g_launchWork->push_back(launch); } - else { - launch(); + catch (const std::exception& e) { + g_outputBuffer = "Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what(); + errlog("Unable to bind to webserver socket on %s: %s", local.toStringWithPort(), e.what()); } } - catch (const std::exception& e) { - g_outputBuffer = "Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what(); - errlog("Unable to bind to webserver socket on %s: %s", local.toStringWithPort(), e.what()); - } }); - typedef LuaAssociativeTable>> webserveropts_t; + using webserveropts_t = LuaAssociativeTable>>; luaCtx.writeFunction("setWebserverConfig", [](boost::optional vars) { setLuaSideEffect(); @@ -1321,64 +1318,65 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) return; } - bool hashPlaintextCredentials = false; - getOptionalValue(vars, "hashPlaintextCredentials", hashPlaintextCredentials); - - std::string password; - std::string apiKey; - std::string acl; - LuaAssociativeTable headers; - bool statsRequireAuthentication{true}; - bool apiRequiresAuthentication{true}; - bool dashboardRequiresAuthentication{true}; - int maxConcurrentConnections = 0; - - if (getOptionalValue(vars, "password", password) > 0) { - auto holder = make_unique(std::move(password), hashPlaintextCredentials); - if (!holder->wasHashed() && holder->isHashingAvailable()) { - infolog("Passing a plain-text password via the 'password' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead."); + dnsdist::configuration::updateRuntimeConfiguration([&vars](dnsdist::configuration::RuntimeConfiguration& config) { + std::string password; + std::string apiKey; + std::string acl; + LuaAssociativeTable headers; + bool statsRequireAuthentication{true}; + bool apiRequiresAuthentication{true}; + bool dashboardRequiresAuthentication{true}; + bool hashPlaintextCredentials = false; + getOptionalValue(vars, "hashPlaintextCredentials", hashPlaintextCredentials); + + if (getOptionalValue(vars, "password", password) > 0) { + auto holder = std::make_shared(std::move(password), hashPlaintextCredentials); + if (!holder->wasHashed() && holder->isHashingAvailable()) { + infolog("Passing a plain-text password via the 'password' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead."); + } + config.d_webPassword = std::move(holder); } - setWebserverPassword(std::move(holder)); - } - - if (getOptionalValue(vars, "apiKey", apiKey) > 0) { - auto holder = make_unique(std::move(apiKey), hashPlaintextCredentials); - if (!holder->wasHashed() && holder->isHashingAvailable()) { - infolog("Passing a plain-text API key via the 'apiKey' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead."); + if (getOptionalValue(vars, "apiKey", apiKey) > 0) { + auto holder = std::make_shared(std::move(apiKey), hashPlaintextCredentials); + if (!holder->wasHashed() && holder->isHashingAvailable()) { + infolog("Passing a plain-text API key via the 'apiKey' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead."); + } + config.d_webAPIKey = std::move(holder); } - setWebserverAPIKey(std::move(holder)); - } - - if (getOptionalValue(vars, "acl", acl) > 0) { - setWebserverACL(acl); - } + if (getOptionalValue(vars, "acl", acl) > 0) { + NetmaskGroup ACLnmg; + ACLnmg.toMasks(acl); + config.d_webServerACL = std::move(ACLnmg); + } - if (getOptionalValue(vars, "customHeaders", headers) > 0) { - setWebserverCustomHeaders(headers); - } + if (getOptionalValue(vars, "customHeaders", headers) > 0) { + config.d_webCustomHeaders = std::move(headers); + } - if (getOptionalValue(vars, "statsRequireAuthentication", statsRequireAuthentication) > 0) { - setWebserverStatsRequireAuthentication(statsRequireAuthentication); - } + if (getOptionalValue(vars, "statsRequireAuthentication", statsRequireAuthentication) > 0) { + config.d_statsRequireAuthentication = statsRequireAuthentication; + } - if (getOptionalValue(vars, "apiRequiresAuthentication", apiRequiresAuthentication) > 0) { - setWebserverAPIRequiresAuthentication(apiRequiresAuthentication); - } + if (getOptionalValue(vars, "apiRequiresAuthentication", apiRequiresAuthentication) > 0) { + config.d_apiRequiresAuthentication = apiRequiresAuthentication; + } - if (getOptionalValue(vars, "dashboardRequiresAuthentication", dashboardRequiresAuthentication) > 0) { - setWebserverDashboardRequiresAuthentication(dashboardRequiresAuthentication); - } + if (getOptionalValue(vars, "dashboardRequiresAuthentication", dashboardRequiresAuthentication) > 0) { + config.d_dashboardRequiresAuthentication = dashboardRequiresAuthentication; + } + }); + int maxConcurrentConnections = 0; if (getOptionalIntegerValue("setWebserverConfig", vars, "maxConcurrentConnections", maxConcurrentConnections) > 0) { - setWebserverMaxConcurrentConnections(maxConcurrentConnections); + dnsdist::webserver::setMaxConcurrentConnections(maxConcurrentConnections); } }); luaCtx.writeFunction("showWebserverConfig", []() { setLuaNoSideEffect(); - return getWebserverConfig(); + return dnsdist::webserver::getConfig(); }); luaCtx.writeFunction("hashPassword", [](const std::string& password, boost::optional workFactor) { @@ -1393,40 +1391,32 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) ComboAddress local(str, 5199); if (client || configCheck) { - dnsdist::configuration::updateImmutableConfiguration([&local](dnsdist::configuration::Configuration& config) { - config.d_consoleServerAddress = local; - }); return; } - dnsdist::configuration::updateRuntimeConfiguration([](dnsdist::configuration::RuntimeConfiguration& config) { + dnsdist::configuration::updateRuntimeConfiguration([local](dnsdist::configuration::RuntimeConfiguration& config) { + config.d_consoleServerAddress = local; config.d_consoleEnabled = true; }); #if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO) - if (dnsdist::configuration::isConfigurationDone() && dnsdist::configuration::getImmutableConfiguration().d_consoleKey.empty()) { + if (dnsdist::configuration::isConfigurationDone() && dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey.empty()) { warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set"); } #endif - try { - auto sock = std::make_shared(local.sin4.sin_family, SOCK_STREAM, 0); - sock->bind(local, true); - sock->listen(5); - auto launch = [sock = std::move(sock), local]() { - std::thread consoleControlThread(dnsdist::console::controlThread, std::move(sock), local); + if (dnsdist::configuration::isConfigurationDone()) { + try { + auto sock = Socket(local.sin4.sin_family, SOCK_STREAM, 0); + sock.bind(local, true); + sock.listen(5); + std::thread consoleControlThread(dnsdist::console::controlThread, std::move(sock)); consoleControlThread.detach(); - }; - if (g_launchWork) { - g_launchWork->emplace_back(std::move(launch)); } - else { - launch(); + catch (const std::exception& exp) { + g_outputBuffer = "Unable to bind to control socket on " + local.toStringWithPort() + ": " + exp.what(); + errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), exp.what()); } } - catch (std::exception& e) { - g_outputBuffer = "Unable to bind to control socket on " + local.toStringWithPort() + ": " + e.what(); - errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), e.what()); - } }); luaCtx.writeFunction("addConsoleACL", [](const std::string& netmask) { @@ -1514,7 +1504,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) }); luaCtx.writeFunction("setKey", [](const std::string& key) { - if (!dnsdist::configuration::isConfigurationDone() && !dnsdist::configuration::getImmutableConfiguration().d_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf + if (!dnsdist::configuration::isConfigurationDone() && !dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf return; // but later setKeys() trump the -k value again } #if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO) @@ -1529,7 +1519,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) return; } - dnsdist::configuration::updateImmutableConfiguration([&newKey](dnsdist::configuration::Configuration& config) { + dnsdist::configuration::updateRuntimeConfiguration([&newKey](dnsdist::configuration::RuntimeConfiguration& config) { config.d_consoleKey = std::move(newKey); }); }); @@ -1542,7 +1532,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) setLuaNoSideEffect(); #if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO) try { - const auto& consoleKey = dnsdist::configuration::getImmutableConfiguration().d_consoleKey; + const auto& consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey; string testmsg; if (optTestMsg) { @@ -3379,7 +3369,7 @@ vector> setupLua(LuaContext& luaCtx, bool client, bool { // this needs to exist only during the parsing of the configuration // and cannot be captured by lambdas - g_launchWork = std::vector>(); + s_launchWork = std::vector>(); setupLuaActions(luaCtx); setupLuaConfig(luaCtx, client, configCheck); @@ -3415,7 +3405,8 @@ vector> setupLua(LuaContext& luaCtx, bool client, bool luaCtx.executeCode(ifs); - auto ret = *g_launchWork; - g_launchWork = boost::none; + auto ret = std::move(*s_launchWork); + s_launchWork = std::nullopt; + return ret; } diff --git a/pdns/dnsdistdist/dnsdist-lua.hh b/pdns/dnsdistdist/dnsdist-lua.hh index 0e076880a1d9..48d3ed2df59d 100644 --- a/pdns/dnsdistdist/dnsdist-lua.hh +++ b/pdns/dnsdistdist/dnsdist-lua.hh @@ -170,7 +170,7 @@ std::shared_ptr makeRule(const luadnsrule_t& var, const std::string& ca void parseRuleParams(boost::optional& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder); void checkParameterBound(const std::string& parameter, uint64_t value, size_t max = std::numeric_limits::max()); -vector> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config); +std::vector> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config); void setupLuaActions(LuaContext& luaCtx); void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck); void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client); diff --git a/pdns/dnsdistdist/dnsdist-metrics.cc b/pdns/dnsdistdist/dnsdist-metrics.cc index 3c8986d0568c..4c647db017be 100644 --- a/pdns/dnsdistdist/dnsdist-metrics.cc +++ b/pdns/dnsdistdist/dnsdist-metrics.cc @@ -178,7 +178,7 @@ std::optional declareCustomMetric(const std::string& name, const st if (itp.second) { g_stats.entries.write_lock()->emplace_back(Stats::EntryPair{name, &(*customCounters)[name].d_value}); dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName}; - addMetricDefinition(def); + dnsdist::webserver::addMetricDefinition(def); } } else if (type == "gauge") { @@ -187,7 +187,7 @@ std::optional declareCustomMetric(const std::string& name, const st if (itp.second) { g_stats.entries.write_lock()->emplace_back(Stats::EntryPair{name, &(*customGauges)[name].d_value}); dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName}; - addMetricDefinition(def); + dnsdist::webserver::addMetricDefinition(def); } } else { diff --git a/pdns/dnsdistdist/dnsdist-web.cc b/pdns/dnsdistdist/dnsdist-web.cc index 787c225bca30..3fbbd1180fcc 100644 --- a/pdns/dnsdistdist/dnsdist-web.cc +++ b/pdns/dnsdistdist/dnsdist-web.cc @@ -45,105 +45,6 @@ #include "threadname.hh" #include "sstuff.hh" -struct WebserverConfig -{ - WebserverConfig() - { - acl.toMasks("127.0.0.1, ::1"); - } - - NetmaskGroup acl; - std::unique_ptr password; - std::unique_ptr apiKey; - boost::optional> customHeaders; - bool apiRequiresAuthentication{true}; - bool dashboardRequiresAuthentication{true}; - bool statsRequireAuthentication{true}; -}; - -LockGuarded g_webserverConfig; - -static ConcurrentConnectionManager s_connManager(100); - -std::string getWebserverConfig() -{ - ostringstream out; - - { - auto config = g_webserverConfig.lock(); - out << "Current web server configuration:" << endl; - out << "ACL: " << config->acl.toString() << endl; - out << "Custom headers: "; - if (config->customHeaders) { - out << endl; - for (const auto& header : *config->customHeaders) { - out << " - " << header.first << ": " << header.second << endl; - } - } - else { - out << "None" << endl; - } - out << "API requires authentication: " << (config->apiRequiresAuthentication ? "yes" : "no") << endl; - out << "Dashboard requires authentication: " << (config->dashboardRequiresAuthentication ? "yes" : "no") << endl; - out << "Statistics require authentication: " << (config->statsRequireAuthentication ? "yes" : "no") << endl; - out << "Password: " << (config->password ? "set" : "unset") << endl; - out << "API key: " << (config->apiKey ? "set" : "unset") << endl; - } - { - const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration(); - out << "API writable: " << (config.d_apiReadWrite ? "yes" : "no") << endl; - out << "API configuration directory: " << config.d_apiConfigDirectory << endl; - out << "Maximum concurrent connections: " << s_connManager.getMaxConcurrentConnections() << endl; - } - - return out.str(); -} - -class WebClientConnection -{ -public: - WebClientConnection(const ComboAddress& client, int socketDesc) : - d_client(client), d_socket(socketDesc) - { - if (!s_connManager.registerConnection()) { - throw std::runtime_error("Too many concurrent web client connections"); - } - } - WebClientConnection(WebClientConnection&& rhs) noexcept : - d_client(rhs.d_client), d_socket(std::move(rhs.d_socket)) - { - } - WebClientConnection(const WebClientConnection&) = delete; - WebClientConnection& operator=(const WebClientConnection&) = delete; - WebClientConnection& operator=(WebClientConnection&& rhs) noexcept - { - d_client = rhs.d_client; - d_socket = std::move(rhs.d_socket); - return *this; - } - - ~WebClientConnection() - { - if (d_socket.getHandle() != -1) { - s_connManager.releaseConnection(); - } - } - - [[nodiscard]] const Socket& getSocket() const - { - return d_socket; - } - - [[nodiscard]] const ComboAddress& getClient() const - { - return d_client; - } - -private: - ComboAddress d_client; - Socket d_socket; -}; - #ifndef DISABLE_PROMETHEUS static MetricDefinitionStorage s_metricDefinitions; @@ -228,6 +129,86 @@ std::map MetricDefinitionStorage::metrics{ }; #endif /* DISABLE_PROMETHEUS */ +namespace dnsdist::webserver +{ +static ConcurrentConnectionManager s_connManager(100); + +std::string getConfig() +{ + ostringstream out; + + { + const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration(); + out << "Current web server configuration:" << endl; + out << "ACL: " << config.d_webServerACL.toString() << endl; + out << "Custom headers: "; + if (config.d_webCustomHeaders) { + out << endl; + for (const auto& header : *config.d_webCustomHeaders) { + out << " - " << header.first << ": " << header.second << endl; + } + } + else { + out << "None" << endl; + } + out << "API requires authentication: " << (config.d_apiRequiresAuthentication ? "yes" : "no") << endl; + out << "Dashboard requires authentication: " << (config.d_dashboardRequiresAuthentication ? "yes" : "no") << endl; + out << "Statistics require authentication: " << (config.d_statsRequireAuthentication ? "yes" : "no") << endl; + out << "Password: " << (config.d_webPassword ? "set" : "unset") << endl; + out << "API key: " << (config.d_webAPIKey ? "set" : "unset") << endl; + out << "API writable: " << (config.d_apiReadWrite ? "yes" : "no") << endl; + out << "API configuration directory: " << config.d_apiConfigDirectory << endl; + out << "Maximum concurrent connections: " << s_connManager.getMaxConcurrentConnections() << endl; + } + + return out.str(); +} + +class WebClientConnection +{ +public: + WebClientConnection(const ComboAddress& client, int socketDesc) : + d_client(client), d_socket(socketDesc) + { + if (!s_connManager.registerConnection()) { + throw std::runtime_error("Too many concurrent web client connections"); + } + } + WebClientConnection(WebClientConnection&& rhs) noexcept : + d_client(rhs.d_client), d_socket(std::move(rhs.d_socket)) + { + } + WebClientConnection(const WebClientConnection&) = delete; + WebClientConnection& operator=(const WebClientConnection&) = delete; + WebClientConnection& operator=(WebClientConnection&& rhs) noexcept + { + d_client = rhs.d_client; + d_socket = std::move(rhs.d_socket); + return *this; + } + + ~WebClientConnection() + { + if (d_socket.getHandle() != -1) { + s_connManager.releaseConnection(); + } + } + + [[nodiscard]] const Socket& getSocket() const + { + return d_socket; + } + + [[nodiscard]] const ComboAddress& getClient() const + { + return d_client; + } + +private: + ComboAddress d_client; + Socket d_socket; +}; + bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def) { #ifndef DISABLE_PROMETHEUS @@ -280,7 +261,7 @@ static void apiSaveACL(const NetmaskGroup& nmg) } #endif /* DISABLE_WEB_CONFIG */ -static bool checkAPIKey(const YaHTTP::Request& req, const std::unique_ptr& apiKey) +static bool checkAPIKey(const YaHTTP::Request& req, const std::shared_ptr& apiKey) { if (!apiKey) { return false; @@ -294,7 +275,7 @@ static bool checkAPIKey(const YaHTTP::Request& req, const std::unique_ptr& password, bool dashboardRequiresAuthentication) +static bool checkWebPassword(const YaHTTP::Request& req, const std::shared_ptr& password, bool dashboardRequiresAuthentication) { if (!dashboardRequiresAuthentication) { return true; @@ -341,26 +322,26 @@ static bool isAStatsRequest(const YaHTTP::Request& req) static bool handleAuthorization(const YaHTTP::Request& req) { - auto config = g_webserverConfig.lock(); + const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration(); if (isAStatsRequest(req)) { - if (config->statsRequireAuthentication) { + if (config.d_statsRequireAuthentication) { /* Access to the stats is allowed for both API and Web users */ - return checkAPIKey(req, config->apiKey) || checkWebPassword(req, config->password, config->dashboardRequiresAuthentication); + return checkAPIKey(req, config.d_webAPIKey) || checkWebPassword(req, config.d_webPassword, config.d_dashboardRequiresAuthentication); } return true; } if (isAnAPIRequest(req)) { /* Access to the API requires a valid API key */ - if (!config->apiRequiresAuthentication || checkAPIKey(req, config->apiKey)) { + if (!config.d_apiRequiresAuthentication || checkAPIKey(req, config.d_webAPIKey)) { return true; } - return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password, config->dashboardRequiresAuthentication); + return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config.d_webPassword, config.d_dashboardRequiresAuthentication); } - return checkWebPassword(req, config->password, config->dashboardRequiresAuthentication); + return checkWebPassword(req, config.d_webPassword, config.d_dashboardRequiresAuthentication); } static bool isMethodAllowed(const YaHTTP::Request& req) @@ -385,7 +366,8 @@ static bool isMethodAllowed(const YaHTTP::Request& req) static bool isClientAllowedByACL(const ComboAddress& remote) { - return g_webserverConfig.lock()->acl.match(remote); + const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration(); + return config.d_webServerACL.match(remote); } static void handleCORS(const YaHTTP::Request& req, YaHTTP::Response& resp) @@ -411,7 +393,7 @@ static void handleCORS(const YaHTTP::Request& req, YaHTTP::Response& resp) } } -static void addSecurityHeaders(YaHTTP::Response& resp, const boost::optional>& customHeaders) +static void addSecurityHeaders(YaHTTP::Response& resp, const std::optional>& customHeaders) { static const std::vector> headers = { {"X-Content-Type-Options", "nosniff"}, @@ -432,7 +414,7 @@ static void addSecurityHeaders(YaHTTP::Response& resp, const boost::optional>& customHeaders) +static void addCustomHeaders(YaHTTP::Response& resp, const std::optional>& customHeaders) { if (!customHeaders) { return; @@ -1453,7 +1435,7 @@ static void handleConfigDump(const YaHTTP::Request& req, YaHTTP::Response& resp) std::vector> configEntries{ {"acl", runtimeConfiguration.d_ACL.toString()}, {"allow-empty-response", runtimeConfiguration.d_allowEmptyResponse}, - {"control-socket", immutableConfig.d_consoleServerAddress.toStringWithPort()}, + {"control-socket", runtimeConfiguration.d_consoleServerAddress.toStringWithPort()}, {"ecs-override", runtimeConfiguration.d_ecsOverride}, {"ecs-source-prefix-v4", static_cast(runtimeConfiguration.d_ECSSourcePrefixV4)}, {"ecs-source-prefix-v6", static_cast(runtimeConfiguration.d_ECSSourcePrefixV6)}, @@ -1858,10 +1840,9 @@ static void connectionThread(WebClientConnection&& conn) resp.version = req.version; { - auto config = g_webserverConfig.lock(); - - addCustomHeaders(resp, config->customHeaders); - addSecurityHeaders(resp, config->customHeaders); + const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration(); + addCustomHeaders(resp, config.d_webCustomHeaders); + addSecurityHeaders(resp, config.d_webCustomHeaders); } /* indicate that the connection will be closed after completion of the response */ resp.headers["Connection"] = "close"; @@ -1926,64 +1907,20 @@ static void connectionThread(WebClientConnection&& conn) } } -void setWebserverAPIKey(std::unique_ptr&& apiKey) -{ - auto config = g_webserverConfig.lock(); - - if (apiKey) { - config->apiKey = std::move(apiKey); - } - else { - config->apiKey.reset(); - } -} - -void setWebserverPassword(std::unique_ptr&& password) -{ - g_webserverConfig.lock()->password = std::move(password); -} - -void setWebserverACL(const std::string& acl) -{ - NetmaskGroup newACL; - newACL.toMasks(acl); - - g_webserverConfig.lock()->acl = std::move(newACL); -} - -void setWebserverCustomHeaders(const boost::optional>& customHeaders) -{ - g_webserverConfig.lock()->customHeaders = customHeaders; -} - -void setWebserverStatsRequireAuthentication(bool require) -{ - g_webserverConfig.lock()->statsRequireAuthentication = require; -} - -void setWebserverAPIRequiresAuthentication(bool require) -{ - g_webserverConfig.lock()->apiRequiresAuthentication = require; -} - -void setWebserverDashboardRequiresAuthentication(bool require) -{ - g_webserverConfig.lock()->dashboardRequiresAuthentication = require; -} - -void setWebserverMaxConcurrentConnections(size_t max) +void setMaxConcurrentConnections(size_t max) { s_connManager.setMaxConcurrentConnections(max); } -void dnsdistWebserverThread(int sock, const ComboAddress& local) +void WebserverThread(Socket sock) { setThreadName("dnsdist/webserv"); + const auto local = *dnsdist::configuration::getCurrentRuntimeConfiguration().d_webServerAddress; infolog("Webserver launched on %s", local.toStringWithPort()); { - auto config = g_webserverConfig.lock(); - if (!config->password && config->dashboardRequiresAuthentication) { + const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration(); + if (!config.d_webPassword && config.d_dashboardRequiresAuthentication) { warnlog("Webserver launched on %s without a password set!", local.toStringWithPort()); } } @@ -1991,7 +1928,7 @@ void dnsdistWebserverThread(int sock, const ComboAddress& local) for (;;) { try { ComboAddress remote(local); - int fileDesc = SAccept(sock, remote); + int fileDesc = SAccept(sock.getHandle(), remote); if (!isClientAllowedByACL(remote)) { vinfolog("Connection to webserver from client %s is not allowed, closing", remote.toStringWithPort()); @@ -2010,3 +1947,4 @@ void dnsdistWebserverThread(int sock, const ComboAddress& local) } } } +} diff --git a/pdns/dnsdistdist/dnsdist-web.hh b/pdns/dnsdistdist/dnsdist-web.hh index d707e464c52e..1c76a194f813 100644 --- a/pdns/dnsdistdist/dnsdist-web.hh +++ b/pdns/dnsdistdist/dnsdist-web.hh @@ -3,20 +3,12 @@ #include "credentials.hh" #include "dnsdist-prometheus.hh" -void setWebserverAPIKey(std::unique_ptr&& apiKey); -void setWebserverPassword(std::unique_ptr&& password); -void setWebserverACL(const std::string& acl); -void setWebserverCustomHeaders(const boost::optional >& customHeaders); -void setWebserverAPIRequiresAuthentication(bool); -void setWebserverDashboardRequiresAuthentication(bool); -void setWebserverStatsRequireAuthentication(bool); -void setWebserverMaxConcurrentConnections(size_t); - -void dnsdistWebserverThread(int sock, const ComboAddress& local); - +namespace dnsdist::webserver +{ +void WebserverThread(Socket sock); +void setMaxConcurrentConnections(size_t max); void registerBuiltInWebHandlers(); void clearWebHandlers(); - -std::string getWebserverConfig(); - +std::string getConfig(); bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def); +} diff --git a/pdns/dnsdistdist/dnsdist.cc b/pdns/dnsdistdist/dnsdist.cc index 5c74b261c4fe..56b4472ad1fc 100644 --- a/pdns/dnsdistdist/dnsdist.cc +++ b/pdns/dnsdistdist/dnsdist.cc @@ -2995,7 +2995,7 @@ static void parseParameters(int argc, char** argv, CommandLineParameters& cmdLin // NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point exit(EXIT_FAILURE); } - dnsdist::configuration::updateImmutableConfiguration([&consoleKey](dnsdist::configuration::Configuration& config) { + dnsdist::configuration::updateRuntimeConfiguration([&consoleKey](dnsdist::configuration::RuntimeConfiguration& config) { config.d_consoleKey = std::move(consoleKey); }); } @@ -3246,6 +3246,44 @@ static void startFrontends() } } +struct ListeningSockets +{ + Socket d_consoleSocket{-1}; + Socket d_webServerSocket{-1}; +}; + +static ListeningSockets initListeningSockets() +{ + ListeningSockets result; + const auto& currentConfig = dnsdist::configuration::getCurrentRuntimeConfiguration(); + + if (currentConfig.d_consoleEnabled) { + const auto& local = currentConfig.d_consoleServerAddress; + try { + result.d_consoleSocket = Socket(local.sin4.sin_family, SOCK_STREAM, 0); + result.d_consoleSocket.bind(local, true); + result.d_consoleSocket.listen(5); + } + catch (const std::exception& exp) { + errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), exp.what()); + } + } + + if (currentConfig.d_webServerAddress) { + const auto& local = *currentConfig.d_webServerAddress; + try { + result.d_webServerSocket = Socket(local.sin4.sin_family, SOCK_STREAM, 0); + result.d_webServerSocket.bind(local, true); + result.d_webServerSocket.listen(5); + } + catch (const std::exception& exp) { + errlog("Unable to bind to web server socket on %s: %s", local.toStringWithPort(), exp.what()); + } + } + + return result; +} + int main(int argc, char** argv) { try { @@ -3297,7 +3335,7 @@ int main(int argc, char** argv) if (cmdLine.beClient || !cmdLine.command.empty()) { setupLua(*(g_lua.lock()), true, false, cmdLine.config); if (clientAddress != ComboAddress()) { - dnsdist::configuration::updateImmutableConfiguration([&clientAddress](dnsdist::configuration::Configuration& config) { + dnsdist::configuration::updateRuntimeConfiguration([&clientAddress](dnsdist::configuration::RuntimeConfiguration& config) { config.d_consoleServerAddress = clientAddress; }); } @@ -3316,15 +3354,13 @@ int main(int argc, char** argv) acl.addMask(addr); } } - }); - - dnsdist::configuration::updateRuntimeConfiguration([](dnsdist::configuration::RuntimeConfiguration& config) { for (const auto& mask : {"127.0.0.1/8", "::1/128"}) { config.d_consoleACL.addMask(mask); } + config.d_webServerACL.toMasks("127.0.0.1, ::1"); }); - registerBuiltInWebHandlers(); + dnsdist::webserver::registerBuiltInWebHandlers(); if (cmdLine.checkConfig) { setupLua(*(g_lua.lock()), false, true, cmdLine.config); @@ -3406,8 +3442,10 @@ int main(int argc, char** argv) infolog("Console ACL allowing connections from: %s", acls.c_str()); } + auto listeningSockets = initListeningSockets(); + #if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO) - if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleEnabled && dnsdist::configuration::getImmutableConfiguration().d_consoleKey.empty()) { + if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleEnabled && dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey.empty()) { warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set"); } #endif @@ -3434,6 +3472,15 @@ int main(int argc, char** argv) initDoHWorkers(); #endif + if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleEnabled) { + std::thread consoleControlThread(dnsdist::console::controlThread, std::move(listeningSockets.d_consoleSocket)); + consoleControlThread.detach(); + } + if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_webServerAddress) { + std::thread webServerThread(dnsdist::webserver::WebserverThread, std::move(listeningSockets.d_webServerSocket)); + webServerThread.detach(); + } + for (auto& todoItem : todo) { todoItem(); } diff --git a/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc b/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc index 03c6a75759cc..41fd39d46afa 100644 --- a/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc +++ b/pdns/dnsdistdist/test-dnsdist-lua-ffi.cc @@ -33,7 +33,7 @@ #include "dnsparser.hh" #include "dnswriter.hh" -bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def) +bool dnsdist::webserver::addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def) { return true; }