From: Remi Gacogne Date: Thu, 3 Mar 2016 17:35:01 +0000 (+0100) Subject: dnsdist: Allow accessing the API with an optional API key X-Git-Tag: rec-4.0.0-alpha2~18^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F3504%2Fhead;p=thirdparty%2Fpdns.git dnsdist: Allow accessing the API with an optional API key The API key can be specified as an additional, optional parameter to `webserver()`. If present in a X-API-Key header, it allows access to the API URLs: - /api/v1/servers/localhost - /jsonstat Others URLs are still only allowed through basic authentication. --- diff --git a/pdns/README-dnsdist.md b/pdns/README-dnsdist.md index ce81d3b653..592db93ed9 100644 --- a/pdns/README-dnsdist.md +++ b/pdns/README-dnsdist.md @@ -880,7 +880,7 @@ Here are all functions: * Practical * `shutdown()`: shut down `dnsdist` * quit or ^D: exit the console - * `webserver(address, password)`: launch a webserver with stats on that address with that password + * `webserver(address, password [, apiKey])`: launch a webserver with stats on that address with that password * ACL related: * `addACL(netmask)`: add to the ACL set who can use this server * `setACL({netmask, netmask})`: replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index b7f5a6b998..d13d5a1ce5 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -995,7 +995,7 @@ vector> setupLua(bool client, const std::string& confi g_carbon.setState(ours); }); - g_lua.writeFunction("webserver", [client](const std::string& address, const std::string& password) { + g_lua.writeFunction("webserver", [client](const std::string& address, const std::string& password, const boost::optional apiKey) { setLuaSideEffect(); if(client) return; @@ -1005,8 +1005,8 @@ vector> setupLua(bool client, const std::string& confi SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1); SBind(sock, local); SListen(sock, 5); - auto launch=[sock, local, password]() { - thread t(dnsdistWebserverThread, sock, local, password); + auto launch=[sock, local, password, apiKey]() { + thread t(dnsdistWebserverThread, sock, local, password, apiKey ? *apiKey : ""); t.detach(); }; if(g_launchWork) diff --git a/pdns/dnsdist-web.cc b/pdns/dnsdist-web.cc index 007d34238a..c1cdbc22ad 100644 --- a/pdns/dnsdist-web.cc +++ b/pdns/dnsdist-web.cc @@ -14,7 +14,7 @@ #include "base64.hh" -static bool compareAuthorization(YaHTTP::Request& req, const string &expected_password) +static bool compareAuthorization(YaHTTP::Request& req, const string &expected_password, const string& expectedApiKey) { // validate password YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization"); @@ -31,6 +31,17 @@ static bool compareAuthorization(YaHTTP::Request& req, const string &expected_pa // this gets rid of terminating zeros auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), expected_password.c_str()))); } + if (!auth_ok && !expectedApiKey.empty()) { + /* if this is a request for the API, + check if the API key is correct */ + if (req.url.path=="/jsonstat" || + req.url.path=="/api/v1/servers/localhost") { + header = req.headers.find("x-api-key"); + if (header != req.headers.end()) { + auth_ok = (0==strcmp(header->second.c_str(), expectedApiKey.c_str())); + } + } + } return auth_ok; } @@ -41,7 +52,7 @@ static void handleCORS(YaHTTP::Request& req, YaHTTP::Response& resp) 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-Headers"] = "Authorization, X-API-Key"; } resp.headers["Access-Control-Allow-Origin"] = origin->second; @@ -49,7 +60,7 @@ static void handleCORS(YaHTTP::Request& req, YaHTTP::Response& resp) } } -static void connectionThread(int sock, ComboAddress remote, string password) +static void connectionThread(int sock, ComboAddress remote, string password, string apiKey) { using namespace json11; vinfolog("Webserver handling connection from %s", remote.toStringWithPort()); @@ -81,13 +92,15 @@ static void connectionThread(int sock, ComboAddress remote, string password) resp.headers["X-Permitted-Cross-Domain-Policies"] = "none"; resp.headers["X-XSS-Protection"] = "1; mode=block"; resp.headers["Content-Security-Policy"] = "default-src 'self'; style-src 'self' 'unsafe-inline'"; + /* no need to send back the API key if any */ + resp.headers.erase("X-API-Key"); 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)) { + else if (!compareAuthorization(req, password, apiKey)) { YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization"); if (header != req.headers.end()) errlog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, remote.toStringWithPort()); @@ -261,7 +274,7 @@ static void connectionThread(int sock, ComboAddress remote, string password) fclose(fp); } } -void dnsdistWebserverThread(int sock, const ComboAddress& local, const std::string& password) +void dnsdistWebserverThread(int sock, const ComboAddress& local, const std::string& password, const std::string& apiKey) { warnlog("Webserver launched on %s", local.toStringWithPort()); for(;;) { @@ -269,7 +282,7 @@ void dnsdistWebserverThread(int sock, const ComboAddress& local, const std::stri ComboAddress remote(local); int fd = SAccept(sock, remote); vinfolog("Got connection from %s", remote.toStringWithPort()); - std::thread t(connectionThread, fd, remote, password); + std::thread t(connectionThread, fd, remote, password, apiKey); t.detach(); } catch(std::exception& e) { diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index d1c50f326e..953f9044c3 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -489,7 +489,7 @@ std::shared_ptr roundrobin(const NumberedServerVector& servers, int getEDNSZ(const char* packet, unsigned int len); void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent); uint16_t getEDNSOptionCode(const char * packet, size_t len); -void dnsdistWebserverThread(int sock, const ComboAddress& local, const string& password); +void dnsdistWebserverThread(int sock, const ComboAddress& local, const string& password, const string& apiKey); bool getMsgLen32(int fd, uint32_t* len); bool putMsgLen32(int fd, uint32_t len); void* tcpAcceptorThread(void* p);