From: Aki Tuomi Date: Sun, 26 Jun 2016 16:57:43 +0000 (+0300) Subject: Add new lua-auth code with updatePolicy hook X-Git-Tag: dnsdist-1.1.0-beta2~145^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0ecc11586584bc6e998907070da13d5ecfd7b104;p=thirdparty%2Fpdns.git Add new lua-auth code with updatePolicy hook --- diff --git a/pdns/Makefile.am b/pdns/Makefile.am index 450e12c035..89fae83fc7 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -170,6 +170,7 @@ pdns_server_SOURCES = \ lock.hh \ logger.cc logger.hh \ lua-auth.cc lua-auth.hh \ + lua-auth4.cc lua-auth4.hh \ lua-pdns.cc lua-pdns.hh lua-iputils.cc \ mastercommunicator.cc \ md5.hh \ diff --git a/pdns/common_startup.cc b/pdns/common_startup.cc index 5d3d4984db..a9eabf271f 100644 --- a/pdns/common_startup.cc +++ b/pdns/common_startup.cc @@ -172,6 +172,7 @@ void declareArguments() ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom"; ::arg().set("lua-prequery-script", "Lua script with prequery handler (DO NOT USE)")=""; + ::arg().set("lua-dnsupdate-policy-script", "Lua script with DNS update policy handler")=""; ::arg().set("experimental-lua-policy-script", "Lua script for the policy engine")=""; ::arg().setSwitch("traceback-handler","Enable the traceback handler (Linux only)")="yes"; diff --git a/pdns/lua-auth4.cc b/pdns/lua-auth4.cc new file mode 100644 index 0000000000..4a86ef45e7 --- /dev/null +++ b/pdns/lua-auth4.cc @@ -0,0 +1,240 @@ +#include "lua-auth4.hh" +#include "stubresolver.hh" +#include +#include "logger.hh" +#include "dnsparser.hh" +#include "syncres.hh" +#include "namespaces.hh" +#include "rec_channel.hh" +#include "ednssubnet.hh" +#include + +#if !defined(HAVE_LUA) + +AuthLua4::AuthLua4(const std::string& fname) { } +bool AuthLua4::updatePolicy(const DNSName &qname, QType qtype, const DNSName &zonename, DNSPacket *packet) { return false; } + +#else + +#undef L +#include "ext/luawrapper/include/LuaContext.hpp" + +AuthLua4::AuthLua4(const std::string& fname) { + d_lw = std::unique_ptr(new LuaContext); + stubParseResolveConf(); + d_lw->registerFunction("getID", [](dnsheader& dh) { return dh.id; }); + d_lw->registerFunction("getCD", [](dnsheader& dh) { return dh.cd; }); + d_lw->registerFunction("getTC", [](dnsheader& dh) { return dh.tc; }); + d_lw->registerFunction("getRA", [](dnsheader& dh) { return dh.ra; }); + d_lw->registerFunction("getAD", [](dnsheader& dh) { return dh.ad; }); + d_lw->registerFunction("getAA", [](dnsheader& dh) { return dh.aa; }); + d_lw->registerFunction("getRD", [](dnsheader& dh) { return dh.rd; }); + d_lw->registerFunction("getRCODE", [](dnsheader& dh) { return dh.rcode; }); + d_lw->registerFunction("getOPCODE", [](dnsheader& dh) { return dh.opcode; }); + d_lw->registerFunction("getQDCOUNT", [](dnsheader& dh) { return ntohs(dh.qdcount); }); + d_lw->registerFunction("getANCOUNT", [](dnsheader& dh) { return ntohs(dh.ancount); }); + d_lw->registerFunction("getNSCOUNT", [](dnsheader& dh) { return ntohs(dh.nscount); }); + d_lw->registerFunction("getARCOUNT", [](dnsheader& dh) { return ntohs(dh.arcount); }); + + d_lw->writeFunction("newDN", [](const std::string& dom){ return DNSName(dom); }); + d_lw->registerFunction("isPartOf", &DNSName::isPartOf); + d_lw->registerFunction("equal", + [](const DNSName& lhs, const std::string& rhs) { return lhs==DNSName(rhs); }); + d_lw->registerFunction("__eq", &DNSName::operator==); + + d_lw->registerFunction("__eq", &DNSResourceRecord::operator==); + d_lw->registerFunction("__lt", &DNSResourceRecord::operator<); + + d_lw->registerFunction("toString", [](const DNSResourceRecord& rec) { return rec.getZoneRepresentation();} ); + + d_lw->registerFunction("qname", [](DNSResourceRecord& rec) { return rec.qname; }); + d_lw->registerFunction("wildcardname", [](DNSResourceRecord& rec) { return rec.wildcardname; }); + d_lw->registerFunction("content", [](DNSResourceRecord& rec) { return rec.content; }); + d_lw->registerFunction("last_modified", [](DNSResourceRecord& rec) { return rec.last_modified; }); + d_lw->registerFunction("ttl", [](DNSResourceRecord& rec) { return rec.ttl; }); + d_lw->registerFunction("signttl", [](DNSResourceRecord& rec) { return rec.signttl; }); + d_lw->registerFunction("domain_id", [](DNSResourceRecord& rec) { return rec.domain_id; }); + d_lw->registerFunction("qtype", [](DNSResourceRecord& rec) { return rec.qtype.getCode(); }); + d_lw->registerFunction("qclass", [](DNSResourceRecord& rec) { return rec.qclass; }); + d_lw->registerFunction("d_place", [](DNSResourceRecord& rec) { return rec.d_place; }); + d_lw->registerFunction("scopeMask", [](DNSResourceRecord& rec) { return rec.scopeMask; }); + d_lw->registerFunction("auth", [](DNSResourceRecord& rec) { return rec.auth; }); + d_lw->registerFunction("disabled", [](DNSResourceRecord& rec) { return rec.disabled; }); + + d_lw->registerFunction("toString", [](const ComboAddress& ca) { return ca.toString(); }); + d_lw->registerFunction("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); }); + d_lw->registerFunction("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } ); + d_lw->registerFunction("getRaw", [](const ComboAddress& ca) { + if(ca.sin4.sin_family == AF_INET) { + auto t=ca.sin4.sin_addr.s_addr; return string((const char*)&t, 4); + } + else + return string((const char*)&ca.sin6.sin6_addr.s6_addr, 16); + } ); + + d_lw->writeFunction("newCA", [](const std::string& a) { return ComboAddress(a); }); + typedef std::unordered_set cas_t; + d_lw->writeFunction("newCAS", []{ return cas_t(); }); + + + d_lw->registerFunction > >)>("add", + [](cas_t& cas, const boost::variant > >& in) + { + try { + if(auto s = boost::get(&in)) { + cas.insert(ComboAddress(*s)); + } + else if(auto v = boost::get > >(&in)) { + for(const auto& s : *v) + cas.insert(ComboAddress(s.second)); + } + else + cas.insert(boost::get(in)); + } + catch(std::exception& e) { theL() <registerFunction("check",[](const cas_t& cas, const ComboAddress&ca) { + return (bool)cas.count(ca); + }); + + + + d_lw->registerFunction("equal", [](const ComboAddress& lhs, const ComboAddress& rhs) { + return ComboAddress::addressOnlyEqual()(lhs, rhs); + }); + + + d_lw->registerFunction("getNetwork", [](const Netmask& nm) { return nm.getNetwork(); } ); // const reference makes this necessary + d_lw->registerFunction("toString", &Netmask::toString); + d_lw->registerFunction("empty", &Netmask::empty); + + d_lw->writeFunction("newNMG", []() { return NetmaskGroup(); }); + d_lw->registerFunction("addMask", [](NetmaskGroup&nmg, const std::string& mask) + { + nmg.addMask(mask); + }); + + d_lw->registerFunction>&)>("addMasks", [](NetmaskGroup&nmg, const vector>& masks) + { + for(const auto& mask: masks) + nmg.addMask(mask.second); + }); + + + d_lw->registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match); + d_lw->registerFunction("toString", [](const DNSName&dn ) { return dn.toString(); }); + d_lw->registerFunction("toStringNoDot", [](const DNSName&dn ) { return dn.toStringNoDot(); }); + d_lw->registerFunction("chopOff", [](DNSName&dn ) { return dn.chopOff(); }); + d_lw->registerMember("name", &DNSRecord::d_name); + d_lw->registerMember("type", &DNSRecord::d_type); + d_lw->registerMember("ttl", &DNSRecord::d_ttl); + + + d_lw->registerFunction("getContent", [](const DNSRecord& dr) { return dr.d_content->getZoneRepresentation(); }); + d_lw->registerFunction(DNSRecord::*)()>("getCA", [](const DNSRecord& dr) { + boost::optional ret; + + if(auto rec = std::dynamic_pointer_cast(dr.d_content)) + ret=rec->getCA(53); + else if(auto rec = std::dynamic_pointer_cast(dr.d_content)) + ret=rec->getCA(53); + return ret; + }); + + + d_lw->registerFunction("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.d_content = shared_ptr(DNSRecordContent::mastermake(dr.d_type, 1, newContent)); }); + + d_lw->writeFunction("pdnslog", [](const std::string& msg, boost::optional loglevel) { + theL() << (Logger::Urgency)loglevel.get_value_or(Logger::Warning) << msg< > in_t; + vector > > pd{ + {"PASS", (int)PolicyDecision::PASS}, {"DROP", (int)PolicyDecision::DROP}, + {"TRUNCATE", (int)PolicyDecision::TRUNCATE} + }; + + vector > rcodes = {{"NOERROR", RCode::NoError }, + {"FORMERR", RCode::FormErr }, + {"SERVFAIL", RCode::ServFail }, + {"NXDOMAIN", RCode::NXDomain }, + {"NOTIMP", RCode::NotImp }, + {"REFUSED", RCode::Refused }, + {"YXDOMAIN", RCode::YXDomain }, + {"YXRRSET", RCode::YXRRSet }, + {"NXRRSET", RCode::NXRRSet }, + {"NOTAUTH", RCode::NotAuth }, + {"NOTZONE", RCode::NotZone }}; + for(const auto& rcode : rcodes) + pd.push_back({rcode.first, rcode.second}); + + pd.push_back({"place", in_t{ + {"QUESTION", 0}, + {"ANSWER", 1}, + {"AUTHORITY", 2}, + {"ADDITIONAL", 3} + }}); + + pd.push_back({"loglevels", in_t{ + {"Alert", LOG_ALERT}, + {"Critical", LOG_CRIT}, + {"Debug", LOG_DEBUG}, + {"Emergency", LOG_EMERG}, + {"Info", LOG_INFO}, + {"Notice", LOG_NOTICE}, + {"Warning", LOG_WARNING}, + {"Error", LOG_ERR} + }}); + + for(const auto& n : QType::names) + pd.push_back({n.first, n.second}); + d_lw->registerMember("tv_sec", &timeval::tv_sec); + d_lw->registerMember("tv_usec", &timeval::tv_usec); + + d_lw->writeVariable("pdns", pd); + + d_lw->writeFunction("resolve", [](const std::string& qname, uint16_t qtype) { + std::vector ret; + std::unordered_map luaResult; + stubDoResolve(qname, qtype, ret); + int i = 0; + for(const auto &row: ret) luaResult[++i] = row; + return luaResult; + }); + +/* update policy */ + d_lw->registerFunction("getQName", [](UpdatePolicyQuery& upq) { return upq.qname; }); + d_lw->registerFunction("getZoneName", [](UpdatePolicyQuery& upq) { return upq.zonename; }); + d_lw->registerFunction("getQType", [](UpdatePolicyQuery& upq) { return upq.qtype; }); + d_lw->registerFunction("getLocal", [](UpdatePolicyQuery& upq) { return upq.local; }); + d_lw->registerFunction("getRemote", [](UpdatePolicyQuery& upq) { return upq.remote; }); + d_lw->registerFunction("getRealRemote", [](UpdatePolicyQuery& upq) { return upq.realRemote; }); + d_lw->registerFunction("getTsigName", [](UpdatePolicyQuery& upq) { return upq.tsigName; }); + d_lw->registerFunction("getPeerPrincipal", [](UpdatePolicyQuery& upq) { return upq.peerPrincipal; }); +/* end of update policy */ + + ifstream ifs(fname); + if(!ifs) { + theL()<executeCode(ifs); + + d_update_policy = d_lw->readVariable>("updatepolicy").get_value_or(0); +} + +bool AuthLua4::updatePolicy(const DNSName &qname, QType qtype, const DNSName &zonename, DNSPacket *packet) { + UpdatePolicyQuery upq; + upq.qname = qname; + upq.qtype = qtype.getCode(); + upq.zonename = zonename; + upq.local = packet->getLocal(); + upq.remote = packet->getRemote(); + upq.realRemote = packet->getRealRemote(); + upq.tsigName = packet->getTSIGKeyname(); + upq.peerPrincipal = packet->d_peer_principal; + + return d_update_policy(upq); +} + +#endif diff --git a/pdns/lua-auth4.hh b/pdns/lua-auth4.hh new file mode 100644 index 0000000000..103460c3d1 --- /dev/null +++ b/pdns/lua-auth4.hh @@ -0,0 +1,40 @@ +#pragma once +#include "iputils.hh" +#include "dnsname.hh" +#include "namespaces.hh" +#include "dnsrecords.hh" +#include "dnspacket.hh" +#include +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +class LuaContext; + +class AuthLua4 : public boost::noncopyable +{ +private: +#ifdef HAVE_LUA + std::unique_ptr d_lw; // this is way on top because it must get destroyed _last_ +#endif + +public: + explicit AuthLua4(const std::string& fname); + bool updatePolicy(const DNSName &qname, QType qtype, const DNSName &zonename, DNSPacket *packet); + + ~AuthLua4(); // this is so unique_ptr works with an incomplete type +private: + struct UpdatePolicyQuery { + DNSName qname; + DNSName zonename; + uint16_t qtype; + ComboAddress local, remote; + Netmask realRemote; + DNSName tsigName; + std::string peerPrincipal; + }; + + typedef std::function luacall_update_policy_t; + + luacall_update_policy_t d_update_policy; +}; diff --git a/pdns/packethandler.cc b/pdns/packethandler.cc index c1a03b376f..57d02bee9f 100644 --- a/pdns/packethandler.cc +++ b/pdns/packethandler.cc @@ -71,7 +71,15 @@ PacketHandler::PacketHandler():B(s_programname), d_dk(&B) { d_pdl = new AuthLua(fname); } - + fname = ::arg()["lua-dnsupdate-policy-script"]; + if (fname.empty()) + { + d_update_policy_lua = NULL; + } + else + { + d_update_policy_lua = new AuthLua4(fname); + } } UeberBackend *PacketHandler::getBackend() diff --git a/pdns/packethandler.hh b/pdns/packethandler.hh index a80c2963ac..886a5840d8 100644 --- a/pdns/packethandler.hh +++ b/pdns/packethandler.hh @@ -30,6 +30,7 @@ #include "packetcache.hh" #include "dnsseckeeper.hh" #include "lua-auth.hh" +#include "lua-auth4.hh" #include "gss_context.hh" #include "namespaces.hh" @@ -109,6 +110,7 @@ private: bool d_doIPv6AdditionalProcessing; bool d_doDNAME; AuthLua* d_pdl; + AuthLua4* d_update_policy_lua; UeberBackend B; // every thread an own instance DNSSECKeeper d_dk; // B is shared with DNSSECKeeper diff --git a/pdns/rfc2136handler.cc b/pdns/rfc2136handler.cc index e37357d353..9c08f8f8b3 100644 --- a/pdns/rfc2136handler.cc +++ b/pdns/rfc2136handler.cc @@ -673,61 +673,66 @@ int PacketHandler::processUpdate(DNSPacket *p) { string msgPrefix="UPDATE (" + itoa(p->d.id) + ") from " + p->getRemote().toString() + " for " + p->qdomain.toLogString() + ": "; L< allowedRanges; - B.getDomainMetadata(p->qdomain, "ALLOW-DNSUPDATE-FROM", allowedRanges); - if (! ::arg()["allow-dnsupdate-from"].empty()) - stringtok(allowedRanges, ::arg()["allow-dnsupdate-from"], ", \t" ); - - NetmaskGroup ng; - for(vector::const_iterator i=allowedRanges.begin(); i != allowedRanges.end(); i++) - ng.addMask(*i); - - if ( ! ng.match(&p->d_remote)) { - L<d_update_policy_lua == NULL) { + // Check permissions - IP based + vector allowedRanges; + B.getDomainMetadata(p->qdomain, "ALLOW-DNSUPDATE-FROM", allowedRanges); + if (! ::arg()["allow-dnsupdate-from"].empty()) + stringtok(allowedRanges, ::arg()["allow-dnsupdate-from"], ", \t" ); - // Check permissions - TSIG based. - vector tsigKeys; - B.getDomainMetadata(p->qdomain, "TSIG-ALLOW-DNSUPDATE", tsigKeys); - if (tsigKeys.size() > 0) { - bool validKey = false; + NetmaskGroup ng; + for(vector::const_iterator i=allowedRanges.begin(); i != allowedRanges.end(); i++) + ng.addMask(*i); - TSIGRecordContent trc; - DNSName inputkey; - string message; - if (! p->getTSIGDetails(&trc, &inputkey, 0)) { - L<d_remote)) { + L<d_tsig_algo == TSIG_GSS) { - GssName inputname(p->d_peer_principal); // match against principal since GSS - for(vector::const_iterator key=tsigKeys.begin(); key != tsigKeys.end(); key++) { - if (inputname.match(*key)) { - validKey = true; - break; - } + + // Check permissions - TSIG based. + vector tsigKeys; + B.getDomainMetadata(p->qdomain, "TSIG-ALLOW-DNSUPDATE", tsigKeys); + if (tsigKeys.size() > 0) { + bool validKey = false; + + TSIGRecordContent trc; + DNSName inputkey; + string message; + if (! p->getTSIGDetails(&trc, &inputkey, 0)) { + L<::const_iterator key=tsigKeys.begin(); key != tsigKeys.end(); key++) { - if (inputkey == DNSName(*key)) { // because checkForCorrectTSIG has already been performed earlier on, if the names of the ky match with the domain given. THis is valid. - validKey=true; - break; + + if (p->d_tsig_algo == TSIG_GSS) { + GssName inputname(p->d_peer_principal); // match against principal since GSS + for(vector::const_iterator key=tsigKeys.begin(); key != tsigKeys.end(); key++) { + if (inputname.match(*key)) { + validKey = true; + break; + } + } + } else { + for(vector::const_iterator key=tsigKeys.begin(); key != tsigKeys.end(); key++) { + if (inputkey == DNSName(*key)) { // because checkForCorrectTSIG has already been performed earlier on, if the names of the ky match with the domain given. THis is valid. + validKey=true; + break; + } } } - } - if (!validKey) { - L<d_havetsig) - L<d_havetsig) + L<first; if (rr->d_place == DNSResourceRecord::AUTHORITY) { + /* see if it's permitted by policy */ + if (this->d_update_policy_lua != NULL) { + if (this->d_update_policy_lua->updatePolicy(rr->d_name, QType(rr->d_type), di.zone, p) == false) { + L<d_name << "/" << QType(rr->d_type).getName() << ": Not permitted by policy"<d_name << "/" << QType(rr->d_type).getName() << ": Permitted by policy"<d_class == QClass::NONE && rr->d_type == QType::NS && rr->d_name == di.zone) nsRRtoDelete.push_back(rr); else