From: Remi Gacogne Date: Tue, 12 Jan 2016 15:00:36 +0000 (+0100) Subject: dnsdist: Add basic CORS support in the webserver X-Git-Tag: dnsdist-1.0.0-alpha2~81^2~2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=5387cc78ecee70f49fcde9962769679f0069689f;p=pdns dnsdist: Add basic CORS support in the webserver Now that we have removed JSONP support, we need to support Cross-Origin Resource Sharing (CORS) to allow web pages not served by our webserver to access our JSON REST API (well, stats). --- diff --git a/pdns/dnsdist-web.cc b/pdns/dnsdist-web.cc index dc7ce8f63..4920de303 100644 --- a/pdns/dnsdist-web.cc +++ b/pdns/dnsdist-web.cc @@ -14,7 +14,7 @@ #include "base64.hh" -bool compareAuthorization(YaHTTP::Request& req, const string &expected_password) +static bool compareAuthorization(YaHTTP::Request& req, const string &expected_password) { // validate password YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization"); @@ -34,6 +34,20 @@ bool compareAuthorization(YaHTTP::Request& req, const string &expected_password) return auth_ok; } +static void handleCORS(YaHTTP::Request& req, YaHTTP::Response& resp) +{ + YaHTTP::strstr_map_t::iterator origin = req.headers.find("Origin"); + if (origin != req.headers.end()) { + if (req.method == "OPTIONS") { + /* Pre-flight request */ + resp.headers["Access-Control-Allow-Methods"] = "GET"; + resp.headers["Access-Control-Allow-Headers"] = "Authorization"; + } + + resp.headers["Access-Control-Allow-Origin"] = origin->second; + resp.headers["Access-Control-Allow-Credentials"] = "true"; + } +} static void connectionThread(int sock, ComboAddress remote, string password) { @@ -67,57 +81,68 @@ static void connectionThread(int sock, ComboAddress remote, string password) resp.headers["X-XSS-Protection"] = "1; mode=block"; resp.headers["Content-Security-Policy"] = "default-src 'self'; img-src *; style-src 'self' 'unsafe-inline'"; - if (!compareAuthorization(req, password)) { + if(req.method == "OPTIONS") { + /* the OPTIONS method should not require auth, otherwise it breaks CORS */ + handleCORS(req, resp); + resp.status=200; + } + else if (!compareAuthorization(req, password)) { errlog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, remote.toStringWithPort()); resp.status=401; resp.body="

Unauthorized

"; resp.headers["WWW-Authenticate"] = "basic realm=\"PowerDNS\""; } - else if(req.url.path=="/jsonstat" && command=="stats") { + else if(req.method != "GET") { + resp.status=405; + } + else if(req.url.path=="/jsonstat") { + handleCORS(req, resp); resp.status=200; - auto obj=Json::object { - { "packetcache-hits", 0}, - { "packetcache-misses", 0}, - { "over-capacity-drops", 0 }, - { "too-old-drops", 0 }, - { "server-policy", g_policy.getLocal()->name} - }; + if(command=="stats") { + auto obj=Json::object { + { "packetcache-hits", 0}, + { "packetcache-misses", 0}, + { "over-capacity-drops", 0 }, + { "too-old-drops", 0 }, + { "server-policy", g_policy.getLocal()->name} + }; - for(const auto& e : g_stats.entries) { - if(const auto& val = boost::get(&e.second)) - obj.insert({e.first, (int)(*val)->load()}); - else if (const auto& val = boost::get(&e.second)) - obj.insert({e.first, (**val)}); - else - obj.insert({e.first, (int)(*boost::get(&e.second))(e.first)}); + for(const auto& e : g_stats.entries) { + if(const auto& val = boost::get(&e.second)) + obj.insert({e.first, (int)(*val)->load()}); + else if (const auto& val = boost::get(&e.second)) + obj.insert({e.first, (**val)}); + else + obj.insert({e.first, (int)(*boost::get(&e.second))(e.first)}); + } + Json my_json = obj; + resp.body=my_json.dump(); + resp.headers["Content-Type"] = "application/json"; } - Json my_json = obj; - - resp.headers["Content-Type"] = "application/json"; - resp.body=my_json.dump(); - } - else if(req.url.path=="/jsonstat" && command=="dynblocklist") { - resp.status=200; - - Json::object obj; - auto slow = g_dynblockNMG.getCopy(); - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - for(const auto& e: slow) { - if(now < e->second.until ) { - Json::object thing{{"reason", e->second.reason}, {"seconds", (double)(e->second.until.tv_sec - now.tv_sec)}, + else if(command=="dynblocklist") { + Json::object obj; + auto slow = g_dynblockNMG.getCopy(); + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + for(const auto& e: slow) { + if(now < e->second.until ) { + Json::object thing{{"reason", e->second.reason}, {"seconds", (double)(e->second.until.tv_sec - now.tv_sec)}, {"blocks", (double)e->second.blocks} }; - obj.insert({e->first.toString(), thing}); - } + obj.insert({e->first.toString(), thing}); + } + } + Json my_json = obj; + resp.body=my_json.dump(); + resp.headers["Content-Type"] = "application/json"; + } + else { + resp.status=404; } - Json my_json=obj; - - resp.headers["Content-Type"] = "application/json"; - resp.body=my_json.dump(); } else if(req.url.path=="/api/v1/servers/localhost") { + handleCORS(req, resp); resp.status=200; Json::array servers;