From: Christian Hofstaedtler Date: Thu, 6 Feb 2014 17:04:15 +0000 (+0100) Subject: Recursor: Add allow-from(-file) REST API X-Git-Tag: rec-3.6.0-rc1~189^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=41942bb39974ff4c85bf9c7941038e6d5b1195e9;p=thirdparty%2Fpdns.git Recursor: Add allow-from(-file) REST API --- diff --git a/pdns/iputils.hh b/pdns/iputils.hh index 3a9ef70376..decbb606dc 100644 --- a/pdns/iputils.hh +++ b/pdns/iputils.hh @@ -364,6 +364,13 @@ public: return str.str(); } + void toStringVector(vector* vec) const + { + for(container_t::const_iterator iter = d_masks.begin(); iter != d_masks.end(); ++iter) { + vec->push_back(iter->toString()); + } + } + void toMasks(const string &ips) { vector parts; diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index c30a5eada5..88b9b61a79 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -106,7 +106,7 @@ bool g_quiet; bool g_weDistributeQueries; // if true, only 1 thread listens on the incoming query sockets -static __thread NetmaskGroup* t_allowFrom; +__thread NetmaskGroup* t_allowFrom; static NetmaskGroup* g_initialAllowFrom; // new thread needs to be setup with this NetmaskGroup* g_dontQuery; diff --git a/pdns/syncres.hh b/pdns/syncres.hh index b6b4920d9d..0b6d7c1e11 100644 --- a/pdns/syncres.hh +++ b/pdns/syncres.hh @@ -548,6 +548,7 @@ struct RemoteKeeper } }; extern __thread RemoteKeeper* t_remotes; +extern __thread NetmaskGroup* t_allowFrom; string doQueueReloadLuaScript(vector::const_iterator begin, vector::const_iterator end); string doTraceRegex(vector::const_iterator begin, vector::const_iterator end); void parseACLs(); diff --git a/pdns/ws-recursor.cc b/pdns/ws-recursor.cc index e5b857dc2f..4bef015a99 100644 --- a/pdns/ws-recursor.cc +++ b/pdns/ws-recursor.cc @@ -35,6 +35,7 @@ #include "rapidjson/writer.h" #include "webserver.hh" #include "ws-api.hh" +#include "logger.hh" using namespace rapidjson; @@ -60,6 +61,51 @@ static void apiWriteConfigFile(const string& filebasename, const string& content ofconf.close(); } +static void apiServerConfigAllowFrom(HttpRequest* req, HttpResponse* resp) +{ + if (req->method == "PUT") { + Document document; + req->json(document); + + if (!document.IsArray()) { + throw ApiException("Supplied JSON must be an array"); + } + + ostringstream ss; + + // Clear allow-from-file if set, so our changes take effect + ss << "allow-from-file=" << endl; + + // Clear allow-from, and provide a "parent" value + ss << "allow-from=" << endl; + for (SizeType i = 0; i < document.Size(); ++i) { + ss << "allow-from+=" << document[i].GetString() << endl; + } + + apiWriteConfigFile("allow-from", ss.str()); + + parseACLs(); + + // fall through to GET + } else if (req->method != "GET") { + throw HttpMethodNotAllowedException(); + } + + // Return currently configured ACLs + Document document; + document.SetArray(); + + vector entries; + t_allowFrom->toStringVector(&entries); + + BOOST_FOREACH(const string& entry, entries) { + Value jentry(entry.c_str(), document.GetAllocator()); // copy + document.PushBack(jentry, document.GetAllocator()); + } + + resp->setBody(document); +} + RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm) { RecursorControlParser rcp; // inits @@ -73,6 +119,7 @@ RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm) // legacy dispatch d_ws->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat, this, _1, _2)); + d_ws->registerApiHandler("/servers/localhost/config/allow-from", &apiServerConfigAllowFrom); d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig); d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog); d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics); diff --git a/regression-tests.api/.gitignore b/regression-tests.api/.gitignore index b5d3e23b1a..1fd0d77023 100644 --- a/regression-tests.api/.gitignore +++ b/regression-tests.api/.gitignore @@ -5,3 +5,4 @@ /*.controlsocket /named.conf /*.pyc +acl.conf diff --git a/regression-tests.api/runtests.py b/regression-tests.api/runtests.py index c6caeff6e3..742b75d574 100755 --- a/regression-tests.api/runtests.py +++ b/regression-tests.api/runtests.py @@ -17,6 +17,13 @@ options { directory "../regression-tests/zones/"; }; zone "example.com" { type master; file "example.com"; }; """ +ACL_LIST_TPL = """ +# Generated by runtests.py +# local host +127.0.0.1 +::1 +""" + daemon = (len(sys.argv) == 2) and sys.argv[1] or None if daemon not in ('authoritative', 'recursor'): print "Usage: ./runtests (authoritative|recursor)" @@ -48,9 +55,10 @@ if daemon == 'authoritative': pdnscmd = ("../pdns/pdns_server --daemon=no --local-port=5300 --socket-dir=./ --no-shuffle --launch=gsqlite3 --gsqlite3-dnssec --send-root-referral --allow-2136-from=127.0.0.0/8 --experimental-rfc2136=yes --cache-ttl=0 --no-config --gsqlite3-database="+SQLITE_DB+" --experimental-json-interface=yes --webserver=yes --webserver-port="+WEBPORT+" --webserver-address=127.0.0.1 --query-logging --webserver-password="+WEBPASSWORD).split() else: + with open('acl.list', 'w') as acl_list: + acl_list.write(ACL_LIST_TPL) - # No preparations for recursor - pdnscmd = ("../pdns/pdns_recursor --daemon=no --socket-dir=. --local-port=5555 --experimental-json-interface=yes --experimental-webserver=yes --experimental-webserver-port="+WEBPORT+" --experimental-webserver-address=127.0.0.1 --experimental-webserver-password="+WEBPASSWORD).split() + pdnscmd = ("../pdns/pdns_recursor --daemon=no --socket-dir=. --config-dir=. --allow-from-file=acl.list --local-port=5555 --experimental-json-interface=yes --experimental-webserver=yes --experimental-webserver-port="+WEBPORT+" --experimental-webserver-address=127.0.0.1 --experimental-webserver-password="+WEBPASSWORD).split() # Now run pdns and the tests. diff --git a/regression-tests.api/test_RecursorConfig.py b/regression-tests.api/test_RecursorConfig.py new file mode 100644 index 0000000000..7e10b66e8b --- /dev/null +++ b/regression-tests.api/test_RecursorConfig.py @@ -0,0 +1,22 @@ +import json +import requests +import unittest +from test_helper import ApiTestCase, isRecursor + + +@unittest.skipIf(not isRecursor(), "Only applicable to recursors") +class RecursorConfig(ApiTestCase): + + def test_ConfigAllowFromGet(self): + r = self.session.get(self.url("/servers/localhost/config/allow-from")) + self.assertSuccessJson(r) + + def test_ConfigAllowFromReplace(self): + payload = ["127.0.0.1"] + r = self.session.put( + self.url("/servers/localhost/config/allow-from"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertSuccessJson(r) + data = r.json() + self.assertEquals("127.0.0.1/32", data[0])