From 223bfcad806f5127d973478a0c8004615ab94c2e Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Tue, 1 Jun 2021 15:54:02 +0200 Subject: [PATCH] add filterForward function, plus initialiser helper in newNMG --- docs/lua-records/functions.rst | 27 ++++++++++++++----- docs/lua-records/reference/netmask.rst | 12 ++++++--- pdns/lua-base4.cc | 13 ++++++++- pdns/lua-record.cc | 22 ++++++++++++--- .../docs/lua-scripting/netmask.rst | 9 +++++-- regression-tests.auth-py/test_LuaRecords.py | 12 +++++---- 6 files changed, 74 insertions(+), 21 deletions(-) diff --git a/docs/lua-records/functions.rst b/docs/lua-records/functions.rst index 2f72a8eda7..52bce72556 100644 --- a/docs/lua-records/functions.rst +++ b/docs/lua-records/functions.rst @@ -185,11 +185,10 @@ Reverse DNS functions ~~~~~~~~~~~~~~~~~~~~~ .. warning:: - The reverse DNS functions are under active development. **They may** - **not be safe for production use.** The syntax of these functions may change at any - time. + For :func:`createForward` and :func:`createForward6`, we recommend filtering with :func:`filterForward`, to prevent PowerDNS from generating A/AAAA responses to addresses outside of your network. + Not limiting responses like this may, in some situations, help attackers with impersonation and attacks like such as cookie stealing. -.. function:: createReverse(format) +.. function:: createReverse(format, [exceptions]) Used for generating default hostnames from IPv4 wildcard reverse DNS records, e.g. ``*.0.0.127.in-addr.arpa`` @@ -200,7 +199,8 @@ Reverse DNS functions Returns a formatted hostname based on the format string passed. :param format: A hostname string to format, for example ``%1%.%2%.%3%.%4%.static.example.com``. - + :param exceptions: An optional table of overrides. For example ``{['10.10.10.10'] = 'quad10.example.com.'}`` would, when generating a name for IP ``10.10.10.10``, return ``quad10.example.com`` instead of something like ``10.10.10.10.example.com``. + **Formatting options:** - ``%1%`` to ``%4%`` are individual octets @@ -251,7 +251,7 @@ Reverse DNS functions $ dig +short A 127.0.0.5.static.example.com @ns1.example.com 127.0.0.5 -.. function:: createReverse6(format) +.. function:: createReverse6(format[, exceptions]) Used for generating default hostnames from IPv6 wildcard reverse DNS records, e.g. ``*.1.0.0.2.ip6.arpa`` @@ -265,7 +265,8 @@ Reverse DNS functions Returns a formatted hostname based on the format string passed. :param format: A hostname string to format, for example ``%33%.static6.example.com``. - + :param exceptions: An optional table of overrides. For example ``{['2001:db8::1'] = 'example.example.com.'}`` would, when generating a name for IP ``2001:db8::1``, return ``example.example.com`` instead of something like ``2001--db8.example.com``. + Formatting options: - ``%1%`` to ``%32%`` are individual characters (nibbles) @@ -317,6 +318,18 @@ Reverse DNS functions $ dig +short AAAA 2001-a-b--1.static6.example.com @ns1.example.com 2001:a:b::1 +.. function:: filterForward(address, masks[, fallback]) + + Used for limiting the output of :func:`createForward` and :func:`createForward6` to a set of netmasks. + + :param address: A string containing an address, usually taken directly from :func:`createForward: or :func:`createForward6`. + :param masks: A NetmaskGroup; any address not matching the NMG will be replaced by the fallback address. + :param fallback: A string containing the fallback address. Defaults to ``0.0.0.0`` or ``::``. + + Example:: + + *.static4.example.com IN LUA A "filterForward(createForward(), newNMG():addMasks{'192.0.2.0/24', '10.0.0.0/8'})" + Helper functions ~~~~~~~~~~~~~~~~ diff --git a/docs/lua-records/reference/netmask.rst b/docs/lua-records/reference/netmask.rst index f50ad8f101..8c248c4283 100644 --- a/docs/lua-records/reference/netmask.rst +++ b/docs/lua-records/reference/netmask.rst @@ -86,7 +86,7 @@ They can be matched against netmasks objects: nmg = newNMG() nmg:addMask("127.0.0.0/8") nmg:addMasks({"213.244.168.0/24", "130.161.0.0/16"}) - nmg:addMasks(dofile("bad.ips")) -- contains return {"ip1","ip2"..} + nmg:addMasks(dofile("bad-ips.lua")) -- contains return {"ip1","ip2"..} if nmg:match(dq.remoteaddr) then print("Intercepting query from ", dq.remoteaddr) @@ -94,9 +94,15 @@ They can be matched against netmasks objects: Prefixing a mask with ``!`` excludes that mask from matching. -.. function:: newNMG() -> NetMaskGroup +.. function:: newNMG([masks]) -> NetMaskGroup - Returns a new, empty :class:`NetMaskGroup`. + .. versionchanged:: 4.5.0 + Added the optional ``masks`` parameter. + + Returns a new :class:`NetMaskGroup`. + If no masks are passed, the object is empty. + + :param {str} masks: The masks to add. .. class:: NetMaskGroup diff --git a/pdns/lua-base4.cc b/pdns/lua-base4.cc index ba48fc617a..e00ac5cd2f 100644 --- a/pdns/lua-base4.cc +++ b/pdns/lua-base4.cc @@ -171,7 +171,18 @@ void BaseLua4::prepareContext() { d_lw->registerToStringFunction(&Netmask::toString); // NetmaskGroup - d_lw->writeFunction("newNMG", []() { return NetmaskGroup(); }); + d_lw->writeFunction("newNMG", [](boost::optional>> masks) { + auto nmg = NetmaskGroup(); + + if (masks) { + for(const auto& mask: *masks) { + nmg.addMask(mask.second); + } + } + + return nmg; + }); + // 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); diff --git a/pdns/lua-record.cc b/pdns/lua-record.cc index b81aaa775d..49dd69fd4e 100644 --- a/pdns/lua-record.cc +++ b/pdns/lua-record.cc @@ -579,10 +579,8 @@ static void setupLuaRecords() vector candidates; - // exceptions are relative to zone // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa - // e["1.2.3.4"]="bert.powerdns.com" - should match, easy enough to do - // the issue is with classless delegation.. + // e["1.2.3.4"]="bert.powerdns.com" then provides an exception if(e) { ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0); const auto& uom = *e; @@ -729,6 +727,24 @@ static void setupLuaRecords() return std::string("unknown"); }); + lua.writeFunction("filterForward", [](string address, NetmaskGroup& nmg, boost::optional fallback) { + ComboAddress ca(address); + + if (nmg.match(ComboAddress(address))) { + return address; + } else { + if (fallback) { + return *fallback; + } + + if (ca.isIPv4()) { + return string("0.0.0.0"); + } else { + return string("::"); + } + } + }); + /* * Simplistic test to see if an IP address listens on a certain port * Will return a single IP address from the set of available IP addresses. If diff --git a/pdns/recursordist/docs/lua-scripting/netmask.rst b/pdns/recursordist/docs/lua-scripting/netmask.rst index b6c09f579b..3f2f8aa7ca 100644 --- a/pdns/recursordist/docs/lua-scripting/netmask.rst +++ b/pdns/recursordist/docs/lua-scripting/netmask.rst @@ -94,9 +94,14 @@ They can be matched against netmasks objects: Prefixing a mask with ``!`` excludes that mask from matching. -.. function:: newNMG() -> NetMaskGroup +.. function:: newNMG([masks]) -> NetMaskGroup + .. versionchanged:: 4.6.0 + Added the optional ``masks`` parameter. - Returns a new, empty :class:`NetMaskGroup`. + Returns a new :class:`NetMaskGroup`. + If no masks are passed, the object is empty. + + :param {str} masks: The masks to add. .. class:: NetMaskGroup diff --git a/regression-tests.auth-py/test_LuaRecords.py b/regression-tests.auth-py/test_LuaRecords.py index b585b21712..32ff9f0a32 100644 --- a/regression-tests.auth-py/test_LuaRecords.py +++ b/regression-tests.auth-py/test_LuaRecords.py @@ -117,7 +117,7 @@ any IN TXT "hello there" resolve IN LUA A ";local r=resolve('localhost', 1) local t={{}} for _,v in ipairs(r) do table.insert(t, v:toString()) end return t" -*.createforward IN LUA A "createForward()" +*.createforward IN LUA A "filterForward(createForward(), newNMG{{'1.0.0.0/8', '64.0.0.0/8'}})" *.createreverse IN LUA PTR "createReverse('%5%.example.com', {{['10.10.10.10'] = 'quad10.example.com.'}})" *.createreverse6 IN LUA PTR "createReverse6('%33%.example.com', {{['2001:db8::1'] = 'example.example.com.'}})" @@ -126,7 +126,7 @@ resolve IN LUA A ";local r=resolve('localhost', 1) local t={{}} createforward6.example.org. 3600 IN SOA {soa} createforward6.example.org. 3600 IN NS ns1.example.org. createforward6.example.org. 3600 IN NS ns2.example.org. -* IN LUA AAAA "createForward6()" +* IN LUA AAAA "filterForward(createForward6(), newNMG{{'2000::/3'}}, 'fe80::1')" """ # the separate createforward6 zone is because some of the code in lua-record.cc insists on working relatively to the zone apex } @@ -641,17 +641,19 @@ createforward6.example.org. 3600 IN NS ns2.example.org. "ip40414243": "64.65.66.67", "ipp40414243": "0.0.0.0", "ip4041424": "0.0.0.0", + "2.2.2.2": "0.0.0.0" # filtered }), ".createreverse.example.org." : (dns.rdatatype.PTR, { "4.3.2.1": "1-2-3-4.example.com.", - "10.10.10.10": "quad10.example.com." + "10.10.10.10": "quad10.example.com." # exception }), ".createforward6.example.org." : (dns.rdatatype.AAAA, { - "2001--db8" : "2001::db8" + "2001--db8" : "2001::db8", + "4000-db8--1" : "fe80::1" # filtered, with fallback address override }), ".createreverse6.example.org." : (dns.rdatatype.PTR, { "8.b.d.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.2" : "2001--db8.example.com.", - "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2" : "example.example.com." + "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2" : "example.example.com." # exception }) } -- 2.47.2