* 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 <fstream>
+#include <pwd.h>
+#include <thread>
#if defined (__OpenBSD__) || defined(__NetBSD__)
#include <readline/readline.h>
#include <editline/readline.h>
#endif
-#include <fstream>
-#include "dolog.hh"
#include "ext/json11/json11.hpp"
+#include "dolog.hh"
+#include "dnsdist.hh"
+#include "dnsdist-console.hh"
+#include "sodcrypto.hh"
+
+GlobalStateHolder<NetmaskGroup> g_consoleACL;
vector<pair<struct timeval, string> > 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;
g_confDelta.push_back({now,line});
}
-string historyFile(const bool &ignoreHOME = false)
+static string historyFile(const bool &ignoreHOME = false)
{
string ret;
{
if(g_verbose)
cout<<"Connecting to "<<server.toStringWithPort()<<endl;
+
int fd=socket(server.sin4.sin_family, SOCK_STREAM, 0);
if (fd < 0) {
cerr<<"Unable to connect to "<<server.toStringWithPort()<<endl;
writingNonce.merge(theirs, ours);
if(!command.empty()) {
- string msg=sodEncryptSym(command, g_key, writingNonce);
+ string msg=sodEncryptSym(command, g_consoleKey, writingNonce);
putMsgLen32(fd, (uint32_t) msg.length());
if(!msg.empty())
writen2(fd, msg);
boost::scoped_array<char> 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<<msg;
cout.flush();
}
if(line.empty())
continue;
- string msg=sodEncryptSym(line, g_key, writingNonce);
+ string msg=sodEncryptSym(line, g_consoleKey, writingNonce);
putMsgLen32(fd, (uint32_t) msg.length());
writen2(fd, msg);
uint32_t len;
boost::scoped_array<char> 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<<msg;
cout.flush();
}
/* 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" },
{ "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" },
{ "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" },
}
}
-void controlClientThread(int fd, ComboAddress client)
+static void controlClientThread(int fd, ComboAddress client)
try
{
setTCPNoDelay(fd);
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: "<<line<<endl;
string response;
try {
catch(const LuaContext::SyntaxErrorException& e) {
response = "Error: " + string(e.what()) + ": ";
}
- response = sodEncryptSym(response, g_key, writingNonce);
+ response = sodEncryptSym(response, g_consoleKey, writingNonce);
putMsgLen32(fd, response.length());
writen2(fd, response.c_str(), response.length());
}
close(fd);
}
+void controlThread(int fd, ComboAddress local)
+try
+{
+ ComboAddress client;
+ int sock;
+ auto localACL = g_consoleACL.getLocal();
+ infolog("Accepting control connections on %s", local.toStringWithPort());
+
+ while ((sock = SAccept(fd, client)) >= 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());
+}
--- /dev/null
+/*
+ * 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<NetmaskGroup> g_consoleACL;
+extern const std::vector<ConsoleKeyword> 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);
#include <thread>
#include "dnsdist.hh"
+#include "dnsdist-console.hh"
#include "dnsdist-lua.hh"
#include "base64.hh"
}
});
+ 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<string,vector<pair<int, string>>> 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<string>(&inp)) {
+ nmg.addMask(*str);
+ }
+ else for(const auto& p : boost::get<vector<pair<int,string>>>(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<string> vec;
+ g_consoleACL.getLocal()->toStringVector(&vec);
+
+ for(const auto& s : vec) {
+ g_outputBuffer += s + "\n";
+ }
+ });
+
g_lua.writeFunction("clearQueryCounters", []() {
unsigned int size{0};
{
});
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;
errlog("%s", g_outputBuffer);
}
else
- g_key=newkey;
+ g_consoleKey=newkey;
});
g_lua.writeFunction("testCrypto", [](boost::optional<string> optTestMsg)
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";
#include "dnsdist.hh"
#include "dnsdist-cache.hh"
+#include "dnsdist-console.hh"
#include "dnsdist-ecs.hh"
#include "dnsdist-lua.hh"
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;
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 '"<<optarg<<"'."<<endl;
exit(EXIT_FAILURE);
}
g_ACL.setState(acl);
}
+ auto consoleACL = g_consoleACL.getCopy();
+ consoleACL.addMask("127.0.0.1/8");
+ g_consoleACL.setState(consoleACL);
+
if (g_cmdLine.checkConfig) {
setupLua(true, g_cmdLine.config);
// No exception was thrown
acls += s;
}
infolog("ACL allowing queries from: %s", acls.c_str());
+ vec.clear();
+ acls.clear();
+ g_consoleACL.getLocal()->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;
extern std::vector<std::tuple<ComboAddress, bool, bool, int, std::string, std::set<int>>> g_locals; // not changed at runtime (we hope XXX)
extern std::vector<shared_ptr<TLSFrontend>> g_tlslocals;
extern vector<ClientState*> g_frontends;
-extern std::string g_key; // in theory needs locking
extern bool g_truncateTC;
extern bool g_fixupCase;
extern int g_tcpRecvTimeout;
extern std::atomic<uint16_t> 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<ConsoleKeyword> g_consoleKeywords;
-extern bool g_logConsoleConnections;
-
#ifdef HAVE_EBPF
extern shared_ptr<BPFFilter> g_defaultBPFFilter;
extern std::vector<std::shared_ptr<DynBPFFilter> > g_dynBPFFilters;
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
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 \
--- /dev/null
+../dnsdist-console.hh
\ No newline at end of file
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
[..]
.. 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.
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.
: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.
: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
~~~~~~~~~~~~~~~~~~
: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``