{ "setRingBuffersLockRetries", true, "n", "set the number of attempts to get a non-blocking lock to a ringbuffer shard before blocking" },
{ "setRingBuffersSize", true, "n [, numberOfShards]", "set the capacity of the ringbuffers used for live traffic inspection to `n`, and optionally the number of shards to use to `numberOfShards`" },
{ "setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)" },
+ { "setSecurityPollInterval", true, "n", "set the security polling interval to `n` seconds" },
+ { "setSecurityPollSuffix", true, "suffix", "set the security polling suffix to the specified value" },
{ "setServerPolicy", true, "policy", "set server selection policy to that policy" },
{ "setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'" },
{ "setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" },
#include "dnsdist-ecs.hh"
#include "dnsdist-lua.hh"
#include "dnsdist-rings.hh"
+#include "dnsdist-secpoll.hh"
#include "base64.hh"
#include "dnswriter.hh"
g_PayloadSizeSelfGenAnswers = payloadSize;
});
+ g_lua.writeFunction("setSecurityPollSuffix", [](const std::string& suffix) {
+ if (g_configurationDone) {
+ g_outputBuffer="setSecurityPollSuffix() cannot be used at runtime!\n";
+ return;
+ }
+
+ g_secPollSuffix = suffix;
+ });
+
+ g_lua.writeFunction("setSecurityPollInterval", [](time_t newInterval) {
+ if (newInterval <= 0) {
+ warnlog("setSecurityPollInterval() should be > 0, skipping");
+ g_outputBuffer="setSecurityPollInterval() should be > 0, skipping";
+ }
+
+ g_secPollInterval = newInterval;
+ });
+
g_lua.writeFunction("addTLSLocal", [client](const std::string& addr, boost::variant<std::string, std::vector<std::pair<int,std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int,std::string>>> keyFiles, boost::optional<localbind_t> vars) {
if (client)
return;
#include "dnsdist-ecs.hh"
#include "dnsdist-lua.hh"
#include "dnsdist-rings.hh"
+#include "dnsdist-secpoll.hh"
#include "base64.hh"
#include "delaypipe.hh"
return 0;
}
-void* healthChecksThread()
+static void* secPollThread()
+{
+ setThreadName("dnsdist/secpoll");
+
+ for (;;) {
+ try {
+ doSecPoll(g_secPollSuffix);
+ }
+ catch(...) {
+ }
+ sleep(g_secPollInterval);
+ }
+}
+
+static void* healthChecksThread()
{
setThreadName("dnsdist/healthC");
thread healththread(healthChecksThread);
+ if (!g_secPollSuffix.empty()) {
+ thread secpollthread(secPollThread);
+ secpollthread.detach();
+ }
+
if(g_cmdLine.beSupervised) {
#ifdef HAVE_SYSTEMD
sd_notify(0, "READY=1");
dnsdist-protobuf.cc dnsdist-protobuf.hh \
dnsdist-rings.cc dnsdist-rings.hh \
dnsdist-rules.hh \
+ dnsdist-secpoll.cc dnsdist-secpoll.hh \
dnsdist-snmp.cc dnsdist-snmp.hh \
dnsdist-tcp.cc \
dnsdist-web.cc \
--- /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.
+ */
+
+#include "config.h"
+
+#include <string>
+#include <vector>
+
+#ifdef HAVE_LIBSODIUM
+#include <sodium.h>
+#endif /* HAVE_LIBSODIUM */
+
+#include "dnsparser.hh"
+#include "dolog.hh"
+#include "iputils.hh"
+#include "misc.hh"
+#include "sstuff.hh"
+
+#include "dnsdist-secpoll.hh"
+
+static std::string getFirstTXTAnswer(const std::string& answer)
+{
+ if (answer.size() <= sizeof(struct dnsheader)) {
+ throw std::runtime_error("Looking for a TXT record in an answer smaller than the DNS header");
+ }
+
+ const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(answer.data());
+ PacketReader pr(answer);
+ uint16_t qdcount = ntohs(dh->qdcount);
+ uint16_t ancount = ntohs(dh->ancount);
+
+ DNSName rrname;
+ uint16_t rrtype;
+ uint16_t rrclass;
+
+ size_t idx = 0;
+ /* consume qd */
+ for(; idx < qdcount; idx++) {
+ rrname = pr.getName();
+ rrtype = pr.get16BitInt();
+ rrclass = pr.get16BitInt();
+ (void) rrtype;
+ (void) rrclass;
+ }
+
+ /* parse AN */
+ for (idx = 0; idx < ancount; idx++) {
+ string blob;
+ struct dnsrecordheader ah;
+ rrname = pr.getName();
+ pr.getDnsrecordheader(ah);
+
+ if (ah.d_type == QType::TXT) {
+ string txt;
+ pr.xfrText(txt);
+
+ return txt;
+ }
+ else {
+ pr.xfrBlob(blob);
+ }
+ }
+
+ throw std::runtime_error("No TXT record in answer");
+}
+
+static std::string getSecPollStatus(const std::string& queriedName, int timeout=2)
+{
+ vector<uint8_t> packet;
+ DNSPacketWriter pw(packet, DNSName(queriedName), QType::TXT);
+#ifdef HAVE_LIBSODIUM
+ pw.getHeader()->id = randombytes_random() % 65536;
+#else
+ pw.getHeader()->id = random() % 65536;
+#endif
+ pw.getHeader()->rd = 1;
+
+ const auto& resolversForStub = getResolvers("/etc/resolv.conf");
+
+ for(const auto& dest : resolversForStub) {
+ Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
+ sock.setNonBlocking();
+ sock.connect(dest);
+ sock.send(string(packet.begin(), packet.end()));
+
+ string reply;
+ int ret = waitForData(sock.getHandle(), timeout, 0);
+ if (ret < 0) {
+ if (g_verbose) {
+ warnlog("Error while waiting for the secpoll response from stub resolver %s: %d", dest.toString(), ret);
+ }
+ continue;
+ }
+ else if (ret == 0) {
+ if (g_verbose) {
+ warnlog("Timeout while waiting for the secpoll response from stub resolver %s", dest.toString());
+ }
+ continue;
+ }
+
+ try {
+ sock.read(reply);
+ }
+ catch(const std::exception& e) {
+ if (g_verbose) {
+ warnlog("Error while reading for the secpoll response from stub resolver %s: %s", dest.toString(), e.what());
+ }
+ continue;
+ }
+
+ if (reply.size() <= sizeof(struct dnsheader)) {
+ if (g_verbose) {
+ warnlog("Too short answer of size %d received from the secpoll stub resolver %s", reply.size(), dest.toString());
+ }
+ continue;
+ }
+
+ struct dnsheader d;
+ memcpy(&d, reply.c_str(), sizeof(d));
+ if (d.id != pw.getHeader()->id) {
+ if (g_verbose) {
+ warnlog("Invalid ID (%d / %d) received from the secpoll stub resolver %s", d.id, pw.getHeader()->id, dest.toString());
+ }
+ continue;
+ }
+
+ if (d.rcode != RCode::NoError) {
+ if (g_verbose) {
+ warnlog("Response code '%s' received from the secpoll stub resolver %s for '%s'", RCode::to_s(d.rcode), dest.toString(), queriedName);
+ }
+
+ /* no need to try another resolver if the domain does not exist */
+ if (d.rcode == RCode::NXDomain) {
+ throw std::runtime_error("Unable to get a valid Security Status update");
+ }
+ continue;
+ }
+
+ if (ntohs(d.qdcount) != 1 || ntohs(d.ancount) != 1) {
+ if (g_verbose) {
+ warnlog("Invalid answer (qdcount %d / ancount %d) received from the secpoll stub resolver %s", dest.toString());
+ }
+ continue;
+ }
+
+ return getFirstTXTAnswer(reply);
+ }
+
+ throw std::runtime_error("Unable to get a valid Security Status update");
+}
+
+static bool g_secPollDone{false};
+std::string g_secPollSuffix{"secpoll.powerdns.com."};
+time_t g_secPollInterval{3600};
+
+void doSecPoll(const std::string& suffix)
+{
+ if (suffix.empty()) {
+ return;
+ }
+
+ const std::string pkgv(PACKAGE_VERSION);
+ bool releaseVersion = pkgv.find("0.0.") != 0;
+
+ struct timeval now;
+ gettimeofday(&now, 0);
+
+ const std::string version = "dnsdist-" + std::string(VERSION);
+ std::string queriedName = version.substr(0, 63) + ".security-status." + suffix;
+
+ if (*queriedName.rbegin() != '.') {
+ queriedName += '.';
+ }
+
+ boost::replace_all(queriedName, "+", "_");
+ boost::replace_all(queriedName, "~", "_");
+
+ try {
+ std::string status = getSecPollStatus(queriedName);
+ pair<string, string> split = splitField(unquotify(status), ' ');
+
+ int securityStatus = std::stoi(split.first);
+ std::string securityMessage = split.second;
+
+ if(securityStatus == 1 && !g_secPollDone) {
+ warnlog("Polled security status of version %s at startup, no known issues reported: %s", std::string(VERSION), securityMessage);
+ }
+ if(securityStatus == 2) {
+ errlog("PowerDNS DNSDist Security Update Recommended: %s", securityMessage);
+ }
+ else if(securityStatus == 3) {
+ errlog("PowerDNS DNSDist Security Update Mandatory: %s", securityMessage);
+ }
+
+ g_secPollDone = true;
+ return;
+ }
+ catch(const std::exception& e) {
+ if (releaseVersion) {
+ warnlog("Error while retrieving the security update for version %s: %s", version, e.what());
+ }
+ else if (!g_secPollDone) {
+ infolog("Error while retrieving the security update for version %s: %s", version, e.what());
+ }
+ }
+
+ if (releaseVersion) {
+ warnlog("Could not retrieve security status update for '%s' on %s", pkgv, queriedName);
+ }
+ else if (!g_secPollDone) {
+ infolog("Not validating response for security status update, this is a non-release version.");
+
+ /* for non-released versions, there is no use sending the same message several times,
+ let's just accept that there will be no security polling for this exact version */
+ g_secPollDone = true;
+ }
+}
--- /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
+
+extern std::string g_secPollSuffix;
+extern time_t g_secPollInterval;
+
+void doSecPoll(const std::string& suffix);
:rfc:`RFC 6891 <6891#section-6.2.5>`, values lower than 512 will be treated as equal to 512.
:param int payloadSize: The responder's maximum UDP payload size, in bytes. Default is 1500.
+
+Security Polling
+~~~~~~~~~~~~~~~~
+
+PowerDNS products can poll the security status of their respective versions. This polling, naturally,
+happens over DNS. If the result is that a given version has a security problem, the software will
+report this at level ‘Error’ during startup, and repeatedly during operations, every
+:func:`setSecurityPollInterval` seconds.
+
+By default, security polling happens on the domain ‘secpoll.powerdns.com’, but this can be changed with
+the :func:`setSecurityPollSuffix` function. If this setting is made empty, no polling will take place.
+Organizations wanting to host their own security zones can do so by changing this setting to a domain name
+under their control.
+
+To enable distributors of PowerDNS to signal that they have backported versions, the PACKAGEVERSION
+compilation-time macro can be used to set a distributor suffix.
+
+.. function:: setSecurityPollInterval(interval)
+
+ .. versionadded:: 1.3.3
+
+ Set the interval, in seconds, between two security pollings.
+
+ :param int interval: The interval, in seconds, between two pollings. Default is 3600.
+
+.. function:: setSecurityPollSuffix(suffix)
+
+ .. versionadded:: 1.3.3
+
+ Domain name from which to query security update notifications. Setting this to an empty string disables secpoll.
+
+ :param string suffix: The suffix to use, default is 'secpoll.powerdns.com.'.
return ENOSYS;
#endif /* HAVE_PTHREAD_SETAFFINITY_NP */
}
+
+std::vector<ComboAddress> getResolvers(const std::string& resolvConfPath)
+{
+ std::vector<ComboAddress> results;
+
+ ifstream ifs(resolvConfPath);
+ if (!ifs) {
+ return results;
+ }
+
+ string line;
+ while(std::getline(ifs, line)) {
+ boost::trim_right_if(line, is_any_of(" \r\n\x1a"));
+ boost::trim_left(line); // leading spaces, let's be nice
+
+ string::size_type tpos = line.find_first_of(";#");
+ if (tpos != string::npos) {
+ line.resize(tpos);
+ }
+
+ if (boost::starts_with(line, "nameserver ") || boost::starts_with(line, "nameserver\t")) {
+ vector<string> parts;
+ stringtok(parts, line, " \t,"); // be REALLY nice
+ for(vector<string>::const_iterator iter = parts.begin() + 1; iter != parts.end(); ++iter) {
+ try {
+ results.emplace_back(*iter, 53);
+ }
+ catch(...)
+ {
+ }
+ }
+ }
+ }
+
+ return results;
+}
bool isSettingThreadCPUAffinitySupported();
int mapThreadToCPUList(pthread_t tid, const std::set<int>& cpus);
+
+std::vector<ComboAddress> getResolvers(const std::string& resolvConfPath);
if (stat(LOCAL_RESOLV_CONF_PATH, &st) != -1) {
if (st.st_mtime != s_localResolvConfMtime) {
- ifstream ifs(LOCAL_RESOLV_CONF_PATH);
- string line;
+ std::vector<ComboAddress> resolvers = getResolvers(LOCAL_RESOLV_CONF_PATH);
s_localResolvConfMtime = st.st_mtime;
- if(!ifs)
- return;
- s_resolversForStub.clear();
- while(std::getline(ifs, line)) {
- boost::trim_right_if(line, is_any_of(" \r\n\x1a"));
- boost::trim_left(line); // leading spaces, let's be nice
-
- string::size_type tpos = line.find_first_of(";#");
- if(tpos != string::npos)
- line.resize(tpos);
-
- if(boost::starts_with(line, "nameserver ") || boost::starts_with(line, "nameserver\t")) {
- vector<string> parts;
- stringtok(parts, line, " \t,"); // be REALLY nice
- for(vector<string>::const_iterator iter = parts.begin()+1; iter != parts.end(); ++iter) {
- try {
- s_resolversForStub.push_back(ComboAddress(*iter, 53));
- }
- catch(...)
- {
- }
- }
- }
+ if (resolvers.empty()) {
+ return;
}
+
+ s_resolversForStub = std::move(resolvers);
}
}
}