From 25bcfaec5627808a45d79ae642ec6fcfbff54411 Mon Sep 17 00:00:00 2001 From: bert hubert Date: Sun, 10 Dec 2017 19:50:36 +0100 Subject: [PATCH] push luarecord into maturity, protect it with flags, metadata, fix up documentation --- docs/lua-records.rst | 61 +++++++++---- modules/geoipbackend/geoipbackend.cc | 5 +- pdns/common_startup.cc | 3 + pdns/common_startup.hh | 1 + pdns/dnsbackend.cc | 4 + pdns/lua-record.cc | 123 +++++++++++++++++---------- pdns/minicurl.cc | 9 +- pdns/minicurl.hh | 4 +- pdns/packethandler.cc | 24 ++++++ 9 files changed, 168 insertions(+), 66 deletions(-) diff --git a/docs/lua-records.rst b/docs/lua-records.rst index af023d8b2..4b06652e3 100644 --- a/docs/lua-records.rst +++ b/docs/lua-records.rst @@ -21,6 +21,12 @@ tiny (or larger) `Lua `_ statements. interoperability, and strive to turn this functionalitity into a broadly supported standard. +To enable this feature, either set 'global-lua-record' in the configuration, +or set the 'ENABLE-LUA-RECORD' per-zone metadata item to 1. + +In addition, to benefit from the geographical features, make sure the PowerDNS +launch statement includes the ``geoip`` backend. + Examples -------- @@ -50,6 +56,12 @@ This uses the GeoIP backend to find indications of the geographical location of the requestor and the listed IP addresses. It will return with one of the closest addresses. +``closest`` and ifportup can be combined as follows:: + + www IN LUA A ("ifportup(443, {'192.0.2.1', '192.0.2.2', '198.51.100.1'}" + ", {selector='closest'}) ") + +This will pick from the viable IP addresses the one deemed closest to the user. Record format ------------- @@ -141,12 +153,12 @@ The Lua snippets can query the following variables: IP address of requesting resolver -``ecs-who`` +``ecswho`` ~~~~~~~~~~~ The EDNS Client Subnet, should one have been set on the query. Unset otherwise. -``best-who`` +``bestwho`` ~~~~~~~~~~~~ In absence of ECS, this is set to the IP address of requesting resolver. Otherwise set to the network part of the EDNS Client Subnet supplied by the @@ -155,8 +167,8 @@ resolver. Functions available ------------------- -``ifportup(portnum, {'ip1', 'ip2'})`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``ifportup(portnum, {'ip1', 'ip2'}, options)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Simplistic test to see if an IP address listens on a certain port. Note that both IPv4 and IPv6 addresses can be tested, but that it is an error to list IPv4 addresses on an AAAA record, or IPv6 addresses on an A record. @@ -165,6 +177,12 @@ Will return a single IP address from the set of available IP addresses. If no IP address is available, will return a random element of the set of addresses suppplied for testing. +Various options can be set in the ``options`` parameter: + + - ``selector``: used to pick the IP address from list of viable candidates. Choices include 'closest', 'random', 'hashed'. + - ``source``: Source IP address to check from + + ``ifurlup(url, {{'ip1', 'ip2'}, {ip3}, options)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ More sophisticated test that attempts an actual http(s) connection to @@ -176,7 +194,8 @@ returned. Various options can be set in the ``options`` parameter: - - ``interval``: number of seconds to wait between checks + - ``selector``: used to pick the IP address from list of viable candidates. Choices include 'closest', 'random', 'hashed'. + - ``source``: Source IP address to check from - ``stringmatch``: check ``url`` for this string, only declare 'up' if found @@ -202,8 +221,8 @@ This will return IP address 192.168.1.54 for queries coming from This function also works for CNAME or TXT records. -``whashed({{weight, 'ip1'}, {weight, 'ip2'}})`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``whashed({{weight, 'ip1'}, ..})`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Based on the hash of ``bestwho``, returns an IP address from the list supplied, as weighted by the various ``weight`` parameters. @@ -213,25 +232,32 @@ factors. Performs no uptime checking. -``wrandom({{weight, 'ip1'}, {weight, 'ip2'}})`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``wrandom({{weight, 'ip1'}, ..})`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Returns a random IP address from the list supplied, as weighted by the various ``weight`` parameters. Performs no uptime checking. -``country('NL')`` -~~~~~~~~~~~~~~~~~ -Returns true if the ``best-who`` IP address of the client is within the +``asnum(num)`` or ``asnum({num1,num2..})`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Returns true if the ``bestwho`` IP address is determined to be from +any of the listed AS numbers. + +``country('NL')`` or ``country({'NL',..}) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Returns true if the ``bestwho`` IP address of the client is within the two letter ISO country code passed, as described in :doc:`backends/geoip`. -``continent('EU')`` -~~~~~~~~~~~~~~~~~~~ -Returns true if the ``best-who`` IP address of the client is within the +``continent('EU')`` or ``continent({'EU',..}) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Returns true if the ``bestwho`` IP address of the client is within the continent passed, as described in :doc:`backends/geoip`. Details & Security ------------------ LUA records are synthesized on query. They can also be transferred via AXFR -to other PowerDNS servers. LUA records themselves can not be queried +to other PowerDNS servers. + +LUA records themselves can not be queried however, as this would allow third parties to see load balancing internals they do not need to see. @@ -248,3 +274,6 @@ possible to combine pre-signed DNSSEC zone and LUA records. In other words, the signing key must be available on the server creating answers based on LUA records. +Note that to protect operators, support for the LUA record must be enabled +explicitly, either globally (``global-lua-record``) or per zone +(``ENABLE-LUA-RECORD``=1). diff --git a/modules/geoipbackend/geoipbackend.cc b/modules/geoipbackend/geoipbackend.cc index 1fdb36de2..9fc0b7f7c 100644 --- a/modules/geoipbackend/geoipbackend.cc +++ b/modules/geoipbackend/geoipbackend.cc @@ -87,8 +87,9 @@ GeoIPBackend::GeoIPBackend(const string& suffix) { s_rc++; } -string getGeoForLua(const std::string& ip, GeoIPBackend::GeoIPQueryAttribute qa) +string getGeoForLua(const std::string& ip, int qaint) { + GeoIPBackend::GeoIPQueryAttribute qa((GeoIPBackend::GeoIPQueryAttribute)qaint); try { GeoIPBackend gib; GeoIPLookup gl; @@ -311,7 +312,7 @@ void GeoIPBackend::initialize() { s_domains.clear(); std::swap(s_domains, tmp_domains); - extern std::function g_getGeo; + extern std::function g_getGeo; g_getGeo = getGeoForLua; } diff --git a/pdns/common_startup.cc b/pdns/common_startup.cc index 1070ce001..71479c3c3 100644 --- a/pdns/common_startup.cc +++ b/pdns/common_startup.cc @@ -36,6 +36,7 @@ bool g_anyToTcp; bool g_8bitDNS; +bool g_doGlobalLuaRecord; typedef Distributor DNSDistributor; ArgvMap theArg; @@ -197,6 +198,7 @@ void declareArguments() ::arg().setSwitch("expand-alias", "Expand ALIAS records")="no"; ::arg().setSwitch("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR")="no"; ::arg().setSwitch("8bit-dns", "Allow 8bit dns queries")="no"; + ::arg().setSwitch("global-lua-record", "Process LUA record for all zones")="no"; ::arg().setSwitch("axfr-lower-serial", "Also AXFR a zone from a master with a lower serial")="no"; ::arg().set("lua-axfr-script", "Script to be used to edit incoming AXFRs")=""; @@ -479,6 +481,7 @@ void mainthread() g_anyToTcp = ::arg().mustDo("any-to-tcp"); g_8bitDNS = ::arg().mustDo("8bit-dns"); + g_doGlobalLuaRecord = ::arg().mustDo("global-lua-record"); DNSPacket::s_udpTruncationThreshold = std::max(512, ::arg().asNum("udp-truncation-threshold")); DNSPacket::s_doEDNSSubnetProcessing = ::arg().mustDo("edns-subnet-processing"); diff --git a/pdns/common_startup.hh b/pdns/common_startup.hh index a46e2ba1f..ee2e7a175 100644 --- a/pdns/common_startup.hh +++ b/pdns/common_startup.hh @@ -55,5 +55,6 @@ extern int isGuarded( char ** ); void* carbonDumpThread(void*); extern bool g_anyToTcp; extern bool g_8bitDNS; +extern bool g_doGlobalLuaRecord; #endif // COMMON_STARTUP_HH diff --git a/pdns/dnsbackend.cc b/pdns/dnsbackend.cc index 5fca2b903..9a85c9c05 100644 --- a/pdns/dnsbackend.cc +++ b/pdns/dnsbackend.cc @@ -33,6 +33,10 @@ #include "dnspacket.hh" #include "dns.hh" +// this has to be somewhere central, and not in a file that requires Lua +// this is so the geoipbackend can set this pointer if loaded for lua-record.cc +std::function g_getGeo; + bool DNSBackend::getAuth(const DNSName &target, SOAData *sd) { return this->getSOA(target, *sd); diff --git a/pdns/lua-record.cc b/pdns/lua-record.cc index 9c7f82f7c..ebfee85bc 100644 --- a/pdns/lua-record.cc +++ b/pdns/lua-record.cc @@ -6,20 +6,19 @@ #include "minicurl.hh" #include "ueberbackend.hh" #include -// this is only for the ENUM -#include "../../modules/geoipbackend/geoipbackend.hh" + +#include "../modules/geoipbackend/geoipbackend.hh" // only for the enum /* to do: - global allow-lua-record setting - zone metadata setting - fix compilation/linking with/without geoipbackend - use weak symbol? + block AXFR unless TSIG, or override + + zone metadata setting to enable + unify ifupurl/ifupport add attribute for query source add attribute for certificate chedk add list of current monitors expire them too? - */ class IsUpOracle @@ -33,37 +32,24 @@ private: opts_t opts; bool operator<(const CheckDesc& rhs) const { - return std::make_tuple(rem, url) < - std::make_tuple(rhs.rem, rhs.url); + std::map oopts, rhsoopts; + for(const auto& m : opts) + oopts[m.first]=m.second; + for(const auto& m : rhs.opts) + rhsoopts[m.first]=m.second; + + return std::make_tuple(rem, url, oopts) < + std::make_tuple(rhs.rem, rhs.url, rhsoopts); } }; public: - bool isUp(const ComboAddress& remote); + bool isUp(const ComboAddress& remote, opts_t opts); bool isUp(const ComboAddress& remote, const std::string& url, opts_t opts=opts_t()); + bool isUp(const CheckDesc& cd); private: void checkURLThread(ComboAddress rem, std::string url, opts_t opts); - void checkTCPThread(const ComboAddress& rem) { - CheckDesc cd{rem}; - setDown(cd); - for(bool first=true;;first=false) { - try { - Socket s(rem.sin4.sin_family, SOCK_STREAM); - s.setNonBlocking(); - s.connect(rem, 1); - if(!isUp(rem)) - L< l(d_mutex); - CheckDesc cd{remote}; auto iter = d_statuses.find(cd); if(iter == d_statuses.end()) { - L<second.status; + +} + +bool IsUpOracle::isUp(const ComboAddress& remote, opts_t opts) +{ + CheckDesc cd{remote, "", opts}; + return isUp(cd); } bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, std::unordered_map opts) @@ -148,6 +140,38 @@ bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, std::u return iter->second.status; } +void IsUpOracle::checkTCPThread(ComboAddress rem, opts_t opts) +{ + CheckDesc cd{rem, "", opts}; + setDown(cd); + for(bool first=true;;first=false) { + try { + Socket s(rem.sin4.sin_family, SOCK_STREAM); + s.setNonBlocking(); + ComboAddress src; + if(opts.count("source")) { + src=ComboAddress(opts["source"]); + s.bind(src); + } + s.connect(rem, 1); + if(!isUp(cd)) { + L< g_getGeo; - std::string getGeo(const std::string& ip, GeoIPBackend::GeoIPQueryAttribute qa) { static bool initialized; + extern std::function g_getGeo; if(!g_getGeo) { if(!initialized) { L<& ips) @@ -389,9 +419,13 @@ std::vector> luaSynth(const std::string& code, cons lua.writeFunction("ifportup", [&bestwho](int port, const vector >& ips, const boost::optional> options) { vector candidates; + std::unordered_map opts; + if(options) + opts = *options; + for(const auto& i : ips) { ComboAddress rem(i.second, port); - if(g_up.isUp(rem)) + if(g_up.isUp(rem, opts)) candidates.push_back(rem); } vector ret; @@ -593,7 +627,10 @@ std::vector> luaSynth(const std::string& code, cons auto lr = getRR(dr.dr); lua.executeCode(lr->getCode()); } - }catch(std::exception& e) { cerr<<"Oops: "<toString()); + } curl_easy_setopt(d_curl, CURLOPT_FOLLOWLOCATION, true); /* only allow HTTP, TFTP and SFTP */ curl_easy_setopt(d_curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); @@ -62,9 +65,9 @@ void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem) d_data.clear(); } -std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem) +std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src) { - setupURL(str, rem); + setupURL(str, rem, src); auto res = curl_easy_perform(d_curl); long http_code = 0; curl_easy_getinfo(d_curl, CURLINFO_RESPONSE_CODE, &http_code); diff --git a/pdns/minicurl.hh b/pdns/minicurl.hh index 45edd4b1d..6d70febc3 100644 --- a/pdns/minicurl.hh +++ b/pdns/minicurl.hh @@ -10,11 +10,11 @@ public: MiniCurl(); ~MiniCurl(); MiniCurl& operator=(const MiniCurl&) = delete; - std::string getURL(const std::string& str, const ComboAddress* rem=0); + std::string getURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0); std::string postURL(const std::string& str, const std::string& postdata); private: CURL *d_curl; static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata); std::string d_data; - void setupURL(const std::string& str, const ComboAddress* rem=0); + void setupURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0); }; diff --git a/pdns/packethandler.cc b/pdns/packethandler.cc index b6223e137..a20e354e7 100644 --- a/pdns/packethandler.cc +++ b/pdns/packethandler.cc @@ -350,6 +350,14 @@ bool PacketHandler::getBestWildcard(DNSPacket *p, SOAData& sd, const DNSName &ta DNSName subdomain(target); bool haveSomething=false; + bool doLua=g_doGlobalLuaRecord; + if(!doLua) { + string val; + d_dk.getFromMeta(sd.qname, "ENABLE-LUA-RECORD", val); + doLua = (val=="1"); + } + + wildcard=subdomain; while( subdomain.chopOff() && !haveSomething ) { if (subdomain.empty()) { @@ -359,7 +367,13 @@ bool PacketHandler::getBestWildcard(DNSPacket *p, SOAData& sd, const DNSName &ta } while(B.get(rr)) { if(rr.dr.d_type == QType::LUA) { + if(!doLua) { + DLOG(L<<"Have a wildcard LUA match, but not doing LUA record for this zone"<(rr.dr); if(rec->d_type == QType::CNAME || rec->d_type == p->qtype.getCode()) { // noCache=true; @@ -1079,6 +1093,7 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p) DNSPacket *r=0; bool noCache=false; + bool doLua=g_doGlobalLuaRecord; if(p->d.qr) { // QR bit from dns packet (thanks RA from N) if(d_logDNSDetails) @@ -1297,9 +1312,18 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p) rrset.clear(); haveAlias.trimToLabels(0); weDone = weRedirected = weHaveUnauth = false; + + + if(!doLua) { + string val; + d_dk.getFromMeta(sd.qname, "ENABLE-LUA-RECORD", val); + doLua = (val=="1"); + } while(B.get(rr)) { if(rr.dr.d_type == QType::LUA) { + if(!doLua) + continue; auto rec=getRR(rr.dr); if(rec->d_type == QType::CNAME || rec->d_type == p->qtype.getCode()) { noCache=true; -- 2.40.0