From: Remi Gacogne Date: Tue, 27 Mar 2018 08:24:48 +0000 (+0200) Subject: dnsdist: Restrict remote connection to the console via an ACL X-Git-Tag: dnsdist-1.3.0~8^2~4 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b5521206c4f7d61ef58d6e1e573998b35b9b70e5;p=pdns dnsdist: Restrict remote connection to the console via an ACL --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 403262672..8c03c1d88 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -19,9 +19,10 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "dnsdist.hh" -#include "sodcrypto.hh" -#include "pwd.h" + +#include +#include +#include #if defined (__OpenBSD__) || defined(__NetBSD__) #include @@ -30,15 +31,20 @@ #include #endif -#include -#include "dolog.hh" #include "ext/json11/json11.hpp" +#include "dolog.hh" +#include "dnsdist.hh" +#include "dnsdist-console.hh" +#include "sodcrypto.hh" + +GlobalStateHolder g_consoleACL; vector > g_confDelta; +std::string g_consoleKey; bool g_logConsoleConnections{true}; // MUST BE CALLED UNDER A LOCK - right now the LuaLock -void feedConfigDelta(const std::string& line) +static void feedConfigDelta(const std::string& line) { if(line.empty()) return; @@ -47,7 +53,7 @@ void feedConfigDelta(const std::string& line) g_confDelta.push_back({now,line}); } -string historyFile(const bool &ignoreHOME = false) +static string historyFile(const bool &ignoreHOME = false) { string ret; @@ -71,6 +77,7 @@ void doClient(ComboAddress server, const std::string& command) { if(g_verbose) cout<<"Connecting to "< resp(new char[len]); readn2(fd, resp.get(), len); msg.assign(resp.get(), len); - msg=sodDecryptSym(msg, g_key, readingNonce); + msg=sodDecryptSym(msg, g_consoleKey, readingNonce); cout< resp(new char[len]); readn2(fd, resp.get(), len); msg.assign(resp.get(), len); - msg=sodDecryptSym(msg, g_key, readingNonce); + msg=sodDecryptSym(msg, g_consoleKey, readingNonce); cout< g_consoleKeywords{ /* keyword, function, parameters, description */ { "addACL", true, "netmask", "add to the ACL set who can use this server" }, { "addAction", true, "DNS rule, DNS action [, {uuid=\"UUID\"}]", "add a rule" }, + { "addConsoleACL", true, "netmask", "add a netmask to the console ACL" }, { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" }, { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" }, { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" }, @@ -374,6 +382,7 @@ const std::vector g_consoleKeywords{ { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"}, { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" }, { "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" }, + { "setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks" }, { "setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections" }, { "setDNSSECPool", true, "pool name", "move queries requesting DNSSEC processing to this pool" }, { "setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported" }, @@ -409,6 +418,7 @@ const std::vector g_consoleKeywords{ { "showACL", true, "", "show our ACL set" }, { "showBinds", true, "", "show listening addresses (frontends)" }, { "showCacheHitResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined cache hit response rules, optionally with their UUIDs and optionally truncated to a given width" }, + { "showConsoleACL", true, "", "show our current console ACL set" }, { "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" }, { "showDynBlocks", true, "", "show dynamic blocks in force" }, { "showPools", true, "", "show the available pools" }, @@ -492,7 +502,7 @@ char** my_completion( const char * text , int start, int end) } } -void controlClientThread(int fd, ComboAddress client) +static void controlClientThread(int fd, ComboAddress client) try { setTCPNoDelay(fd); @@ -519,7 +529,7 @@ try readn2(fd, msg.get(), len); string line(msg.get(), len); - line = sodDecryptSym(line, g_key, readingNonce); + line = sodDecryptSym(line, g_consoleKey, readingNonce); // cerr<<"Have decrypted line: "<= 0) { + + if (!localACL->match(client)) { + vinfolog("Control connection from %s dropped because of ACL", client.toStringWithPort()); + close(sock); + continue; + } + + if (g_logConsoleConnections) { + warnlog("Got control connection from %s", client.toStringWithPort()); + } + + std::thread t(controlClientThread, sock, client); + t.detach(); + } +} +catch(const std::exception& e) +{ + close(fd); + errlog("Control connection died: %s", e.what()); +} diff --git a/pdns/dnsdist-console.hh b/pdns/dnsdist-console.hh new file mode 100644 index 000000000..01eb5d48b --- /dev/null +++ b/pdns/dnsdist-console.hh @@ -0,0 +1,51 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#pragma once + +struct ConsoleKeyword { + std::string name; + bool function; + std::string parameters; + std::string description; + std::string toString() const + { + std::string res(name); + if (function) { + res += "(" + parameters + ")"; + } + res += ": "; + res += description; + return res; + } +}; + +extern GlobalStateHolder g_consoleACL; +extern const std::vector g_consoleKeywords; +extern std::string g_consoleKey; // in theory needs locking +extern bool g_logConsoleConnections; + +void doClient(ComboAddress server, const std::string& command); +void doConsole(); +extern "C" { +char** my_completion( const char * text , int start, int end); +} +void controlThread(int fd, ComboAddress local); diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 6f46af20a..7f9e1858f 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -29,6 +29,7 @@ #include #include "dnsdist.hh" +#include "dnsdist-console.hh" #include "dnsdist-lua.hh" #include "base64.hh" @@ -607,6 +608,47 @@ void setupLuaConfig(bool client) } }); + g_lua.writeFunction("addConsoleACL", [](const std::string& netmask) { + setLuaSideEffect(); +#ifndef HAVE_LIBSODIUM + warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications"); +#endif + + g_consoleACL.modify([netmask](NetmaskGroup& nmg) { nmg.addMask(netmask); }); + }); + + g_lua.writeFunction("setConsoleACL", [](boost::variant>> inp) { + setLuaSideEffect(); + +#ifndef HAVE_LIBSODIUM + warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications"); +#endif + + NetmaskGroup nmg; + if(auto str = boost::get(&inp)) { + nmg.addMask(*str); + } + else for(const auto& p : boost::get>>(inp)) { + nmg.addMask(p.second); + } + g_consoleACL.setState(nmg); + }); + + g_lua.writeFunction("showConsoleACL", []() { + setLuaNoSideEffect(); + +#ifndef HAVE_LIBSODIUM + warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications"); +#endif + + vector vec; + g_consoleACL.getLocal()->toStringVector(&vec); + + for(const auto& s : vec) { + g_outputBuffer += s + "\n"; + } + }); + g_lua.writeFunction("clearQueryCounters", []() { unsigned int size{0}; { @@ -647,9 +689,12 @@ void setupLuaConfig(bool client) }); g_lua.writeFunction("setKey", [](const std::string& key) { - if(!g_configurationDone && ! g_key.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf + if(!g_configurationDone && ! g_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf return; // but later setKeys() trump the -k value again } +#ifndef HAVE_LIBSODIUM + warnlog("Calling setKey() while libsodium support has not been enabled is not secure, and will result in cleartext communications"); +#endif setLuaSideEffect(); string newkey; @@ -658,7 +703,7 @@ void setupLuaConfig(bool client) errlog("%s", g_outputBuffer); } else - g_key=newkey; + g_consoleKey=newkey; }); g_lua.writeFunction("testCrypto", [](boost::optional optTestMsg) @@ -678,14 +723,14 @@ void setupLuaConfig(bool client) SodiumNonce sn, sn2; sn.init(); sn2=sn; - string encrypted = sodEncryptSym(testmsg, g_key, sn); - string decrypted = sodDecryptSym(encrypted, g_key, sn2); + string encrypted = sodEncryptSym(testmsg, g_consoleKey, sn); + string decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2); sn.increment(); sn2.increment(); - encrypted = sodEncryptSym(testmsg, g_key, sn); - decrypted = sodDecryptSym(encrypted, g_key, sn2); + encrypted = sodEncryptSym(testmsg, g_consoleKey, sn); + decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2); if(testmsg == decrypted) g_outputBuffer="Everything is ok!\n"; diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 221979a31..8c62d962b 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -43,6 +43,7 @@ #include "dnsdist.hh" #include "dnsdist-cache.hh" +#include "dnsdist-console.hh" #include "dnsdist-ecs.hh" #include "dnsdist-lua.hh" @@ -1859,32 +1860,6 @@ void* healthChecksThread() return 0; } -string g_key; - - -void controlThread(int fd, ComboAddress local) -try -{ - ComboAddress client; - int sock; - warnlog("Accepting control connections on %s", local.toStringWithPort()); - while((sock=SAccept(fd, client)) >= 0) { - if (g_logConsoleConnections) { - warnlog("Got control connection from %s", client.toStringWithPort()); - } - - thread t(controlClientThread, sock, client); - t.detach(); - } -} -catch(std::exception& e) -{ - close(fd); - errlog("Control connection died: %s", e.what()); -} - - - static void bindAny(int af, int sock) { __attribute__((unused)) int one = 1; @@ -2117,7 +2092,7 @@ try break; #ifdef HAVE_LIBSODIUM case 'k': - if (B64Decode(string(optarg), g_key) < 0) { + if (B64Decode(string(optarg), g_consoleKey) < 0) { cerr<<"Unable to decode key '"<toStringVector(&vec); + for (const auto& entry : vec) { + if (!acls.empty()) { + acls += ", "; + } + acls += entry; + } + infolog("Console ACL allowing connections from: %s", acls.c_str()); uid_t newgid=0; gid_t newuid=0; diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 862efa60b..953db257a 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -773,7 +773,6 @@ extern ComboAddress g_serverControl; // not changed during runtime extern std::vector>> g_locals; // not changed at runtime (we hope XXX) extern std::vector> g_tlslocals; extern vector g_frontends; -extern std::string g_key; // in theory needs locking extern bool g_truncateTC; extern bool g_fixupCase; extern int g_tcpRecvTimeout; @@ -798,25 +797,6 @@ extern bool g_useTCPSinglePipe; extern std::atomic g_downstreamTCPCleanupInterval; extern size_t g_udpVectorSize; -struct ConsoleKeyword { - std::string name; - bool function; - std::string parameters; - std::string description; - std::string toString() const - { - std::string res(name); - if (function) { - res += "(" + parameters + ")"; - } - res += ": "; - res += description; - return res; - } -}; -extern const std::vector g_consoleKeywords; -extern bool g_logConsoleConnections; - #ifdef HAVE_EBPF extern shared_ptr g_defaultBPFFilter; extern std::vector > g_dynBPFFilters; @@ -860,12 +840,6 @@ bool getMsgLen32(int fd, uint32_t* len); bool putMsgLen32(int fd, uint32_t len); void* tcpAcceptorThread(void* p); -void doClient(ComboAddress server, const std::string& command); -void doConsole(); -void controlClientThread(int fd, ComboAddress client); -extern "C" { -char** my_completion( const char * text , int start, int end); -} void setLuaNoSideEffect(); // if nothing has been declared, set that there are no side effects void setLuaSideEffect(); // set to report a side effect, cancelling all _no_ side effect calls bool getLuaNoSideEffect(); // set if there were only explicit declarations of _no_ side effect diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index 1bcab9a84..7467ab509 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -86,7 +86,7 @@ dnsdist_SOURCES = \ dnsdist-dynbpf.cc dnsdist-dynbpf.hh \ dnsdist-cache.cc dnsdist-cache.hh \ dnsdist-carbon.cc \ - dnsdist-console.cc \ + dnsdist-console.cc dnsdist-console.hh \ dnsdist-dnscrypt.cc \ dnsdist-ecs.cc dnsdist-ecs.hh \ dnsdist-lua.hh dnsdist-lua.cc \ diff --git a/pdns/dnsdistdist/dnsdist-console.hh b/pdns/dnsdistdist/dnsdist-console.hh new file mode 120000 index 000000000..c92b2ad52 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-console.hh @@ -0,0 +1 @@ +../dnsdist-console.hh \ No newline at end of file diff --git a/pdns/dnsdistdist/docs/guides/console.rst b/pdns/dnsdistdist/docs/guides/console.rst index 7c736dc1e..3238f9db7 100644 --- a/pdns/dnsdistdist/docs/guides/console.rst +++ b/pdns/dnsdistdist/docs/guides/console.rst @@ -11,7 +11,7 @@ The console can be enabled with :func:`controlSocket`: controlSocket('192.0.2.53:5199') -To enable encryption, first generate a key with :func:`makeKey`:: +Exposing the console to the network without encryption enabled is not recommended. To enable encryption, first generate a key with :func:`makeKey`:: $ ./dnsdist -l 127.0.0.1:5300 [..] @@ -37,3 +37,15 @@ Alternatively, you can specify the address and key on the client commandline:: .. warning:: This will leak the key into your shell's history and is **not** recommended. + +Note that encryption requires building dnsdist with libsodium support enabled. + +Since 1.3.0, dnsdist supports restricting which client can connect to the console with an ACL: + +.. code-block:: lua + + controlSocket('192.0.2.53:5199') + setConsoleACL('192.0.2.0/24') + +The default value is '127.0.0.1', restricting the use of the console to local users. Please make sure that encryption is enabled +before using :func:`addConsoleACL` or :func:`setConsoleACL` to allow connection from remote clients. diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index b136c5cea..b629207c4 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -137,9 +137,20 @@ Listen Sockets Control Socket, Console and Webserver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. function:: addConsoleACL(netmask) + + .. versionadded:: 1.3.0 + + Add a netmask to the existing console ACL, allowing remote clients to connect to the console. Please make sure that encryption + has been enabled with :func:`setKey` before doing so. + + :param str netmask: A CIDR netmask, e.g. ``"192.0.2.0/24"``. Without a subnetmask, only the specific address is allowed. + .. function:: controlSocket(address) - Bind to ``addr`` and listen for a connection for the console + Bind to ``addr`` and listen for a connection for the console. Since 1.3.0 only connections from local users are allowed + by default, :func:`addConsoleACL` and :func:`setConsoleACL` can be used to enable remote connections. Please make sure + that encryption has been enabled with :func:`setKey` before doing so. :param str address: An IP address with optional port. By default, the port is 5199. @@ -165,6 +176,19 @@ Control Socket, Console and Webserver :param str key: An encoded key, as generated by :func:`makeKey` +.. function:: setConsoleACL(netmasks) + + .. versionadded:: 1.3.0 + + Remove the existing console ACL and add the netmasks from the table, allowing remote clients to connect to the console. Please make sure that encryption + has been enabled with :func:`setKey` before doing so. + + :param {str} netmasks: A table of CIDR netmask, e.g. ``{"192.0.2.0/24", "2001:DB8:14::/56"}``. Without a subnetmask, only the specific address is allowed. + +.. function:: showConsoleACL() + + Print a list of all netmasks allowed to connect to the console. + .. function:: testCrypto() Test the crypto code, will report errors when something is not ok. @@ -205,6 +229,10 @@ Access Control Lists :param {str} netmasks: A table of CIDR netmask, e.g. ``{"192.0.2.0/24", "2001:DB8:14::/56"}``. Without a subnetmask, only the specific address is allowed. +.. function:: showACL() + + Print a list of all allowed netmasks. + EDNS Client Subnet ~~~~~~~~~~~~~~~~~~ @@ -558,10 +586,6 @@ Status, Statistics and More :param {str} selectors: A lua table of selectors. Only queries matching all selectors are shown :param int num: Show a maximum of ``num`` recent queries, default is 10. -.. function:: showACL() - - Print a list of all allowed netmasks. - .. function:: showBinds() Print a list of all the current addresses and ports dnsdist is listening on, also called ``frontends``