]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Support hashed credentials (password, API key) in the auth and rec
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 30 Mar 2021 15:56:45 +0000 (17:56 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 16 Sep 2021 12:12:27 +0000 (14:12 +0200)
pdns/Makefile.am
pdns/recursordist/Makefile.am
pdns/recursordist/credentials.cc [new symlink]
pdns/recursordist/credentials.hh [new symlink]
pdns/webserver.cc
pdns/webserver.hh

index a17aa95e0aeae8a5c3cdfce2fdeb33ebb0063e9a..6e14a2effa920c8d903573f257c66730142f5363 100644 (file)
@@ -200,6 +200,7 @@ pdns_server_SOURCES = \
        comment.hh \
        common_startup.cc common_startup.hh \
        communicator.cc communicator.hh \
+       credentials.cc credentials.hh \
        dbdnsseckeeper.cc \
        digests.hh \
        distributor.hh \
@@ -332,6 +333,7 @@ pdnsutil_SOURCES = \
        bindparser.yy \
        cachecleaner.hh \
        circular_buffer.hh \
+       credentials.cc credentials.hh \
        dbdnsseckeeper.cc \
        dns.cc \
        dns_random.cc \
@@ -675,6 +677,7 @@ ixfrdist_SOURCES = \
        axfr-retriever.cc \
        base32.cc \
        base64.cc base64.hh \
+       credentials.cc credentials.hh \
        dns.cc \
        dns_random_urandom.cc dns_random.hh \
        dnslabeltext.cc \
@@ -709,7 +712,6 @@ ixfrdist_SOURCES = \
        webserver.hh webserver.cc \
        zoneparser-tng.cc
 
-
 ixfrdist_LDADD = \
        $(BOOST_PROGRAM_OPTIONS_LIBS) \
        $(JSON11_LIBS) \
@@ -722,6 +724,10 @@ ixfrdist_LDFLAGS = \
        $(BOOST_PROGRAM_OPTIONS_LDFLAGS) \
        $(LIBCRYPTO_LDFLAGS)
 
+if LIBSODIUM
+ixfrdist_LDADD += $(LIBSODIUM_LIBS)
+endif
+
 if PKCS11
 ixfrdist_SOURCES += pkcs11signers.cc pkcs11signers.hh
 ixfrdist_LDADD += $(P11KIT1_LIBS)
index e9b9b43a4ae89e6fd3e3816fc4bdc762ec204819..39a5fb3c2428ec6286b85a5989e5be37f65ab146 100644 (file)
@@ -105,6 +105,7 @@ pdns_recursor_SOURCES = \
        capabilities.cc capabilities.hh \
        circular_buffer.hh \
        comment.hh \
+       credentials.cc credentials.hh \
        dns.hh dns.cc \
        dns_random.hh dns_random.cc \
        dnsbackend.hh \
@@ -365,6 +366,9 @@ if LIBSODIUM
 pdns_recursor_SOURCES += \
        sodiumsigners.cc
 pdns_recursor_LDADD += $(LIBSODIUM_LIBS)
+
+rec_control_LDADD = $(LIBSODIUM_LIBS)
+
 testrunner_SOURCES += \
        sodiumsigners.cc
 testrunner_LDADD += $(LIBSODIUM_LIBS)
@@ -439,6 +443,7 @@ endif
 
 rec_control_SOURCES = \
        arguments.cc arguments.hh \
+       credentials.cc credentials.hh \
        dnslabeltext.cc \
        dnsname.hh dnsname.cc \
        logger.cc \
diff --git a/pdns/recursordist/credentials.cc b/pdns/recursordist/credentials.cc
new file mode 120000 (symlink)
index 0000000..385b212
--- /dev/null
@@ -0,0 +1 @@
+../credentials.cc
\ No newline at end of file
diff --git a/pdns/recursordist/credentials.hh b/pdns/recursordist/credentials.hh
new file mode 120000 (symlink)
index 0000000..2c60bff
--- /dev/null
@@ -0,0 +1 @@
+../credentials.hh
\ No newline at end of file
index 26633811c0b93e278e73de3ab9796b232c79d4b9..90ae04043d4b5641ca76b76f039237fa4bb22f75 100644 (file)
@@ -52,10 +52,10 @@ json11::Json HttpRequest::json()
   return doc;
 }
 
-bool HttpRequest::compareAuthorization(const string &expected_password)
+bool HttpRequest::compareAuthorization(const CredentialsHolder& credentials) const
 {
   // validate password
-  YaHTTP::strstr_map_t::iterator header = headers.find("authorization");
+  auto header = headers.find("authorization");
   bool auth_ok = false;
   if (header != headers.end() && toLower(header->second).find("basic ") == 0) {
     string cookie = header->second.substr(6);
@@ -66,22 +66,32 @@ bool HttpRequest::compareAuthorization(const string &expected_password)
     vector<string> cparts;
     stringtok(cparts, plain, ":");
 
-    // this gets rid of terminating zeros
-    auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), expected_password.c_str())));
+    auth_ok = (cparts.size() == 2 && credentials.matches(cparts[1].c_str()));
   }
   return auth_ok;
 }
 
-bool HttpRequest::compareHeader(const string &header_name, const string &expected_value)
+bool HttpRequest::compareHeader(const string &header_name, const string &expected_value) const
 {
-  YaHTTP::strstr_map_t::iterator header = headers.find(header_name);
-  if (header == headers.end())
+  auto header = headers.find(header_name);
+  if (header == headers.end()) {
     return false;
+  }
 
   // this gets rid of terminating zeros
   return (0==strcmp(header->second.c_str(), expected_value.c_str()));
 }
 
+bool HttpRequest::compareHeader(const string &header_name, const CredentialsHolder& credentials) const
+{
+  auto header = headers.find(header_name);
+  if (header == headers.end()) {
+    return false;
+  }
+
+  return credentials.matches(header->second);
+}
+
 void HttpResponse::setPlainBody(const string& document)
 {
   this->headers["Content-Type"] = "text/plain; charset=utf-8";
@@ -153,16 +163,16 @@ void WebServer::apiWrapper(const WebServer::HandlerFunction& handler, HttpReques
 
   resp->headers["access-control-allow-origin"] = "*";
 
-  if (d_apikey.empty()) {
+  if (!d_apikey) {
     g_log<<Logger::Error<<req->logprefix<<"HTTP API Request \"" << req->url.path << "\": Authentication failed, API Key missing in config" << endl;
     throw HttpUnauthorizedException("X-API-Key");
   }
 
-  bool auth_ok = req->compareHeader("x-api-key", d_apikey) || req->getvars["api-key"] == d_apikey;
+  bool auth_ok = req->compareHeader("x-api-key", *d_apikey) || d_apikey->matches(req->getvars["api-key"]);
 
   if (!auth_ok && allowPassword) {
-    if (!d_webserverPassword.empty()) {
-      auth_ok = req->compareAuthorization(d_webserverPassword);
+    if (d_webserverPassword) {
+      auth_ok = req->compareAuthorization(*d_webserverPassword);
     } else {
       auth_ok = true;
     }
@@ -205,8 +215,8 @@ void WebServer::registerApiHandler(const string& url, const HandlerFunction& han
 }
 
 void WebServer::webWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp) {
-  if (!d_webserverPassword.empty()) {
-    bool auth_ok = req->compareAuthorization(d_webserverPassword);
+  if (d_webserverPassword) {
+    bool auth_ok = req->compareAuthorization(*d_webserverPassword);
     if (!auth_ok) {
       g_log<<Logger::Debug<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Web Authentication failed" << endl;
       throw HttpUnauthorizedException("Basic");
index 858b56e9eb7770981008d3a3e95d5352a80adcd6..2fff9c5009c02ea40fdc277295ed3529e0a53a1c 100644 (file)
 #include <list>
 #include <boost/utility.hpp>
 #include <yahttp/yahttp.hpp>
+
 #include "json11.hpp"
+
+#include "credentials.hh"
 #include "namespaces.hh"
 #include "sstuff.hh"
 
@@ -42,8 +45,9 @@ public:
   json11::Json json();
 
   // checks password _only_.
-  bool compareAuthorization(const string &expected_password);
-  bool compareHeader(const string &header_name, const string &expected_value);
+  bool compareAuthorization(const CredentialsHolder& expectedCredentials) const;
+  bool compareHeader(const string &header_name, const CredentialsHolder& expectedCredentials) const;
+  bool compareHeader(const string &header_name, const string &expected_value) const;
 };
 
 class HttpResponse: public YaHTTP::Response {
@@ -160,11 +164,21 @@ public:
   virtual ~WebServer() { };
 
   void setApiKey(const string &apikey) {
-    d_apikey = apikey;
+    if (!apikey.empty()) {
+      d_apikey = make_unique<CredentialsHolder>(std::string(apikey));
+    }
+    else {
+      d_apikey.reset();
+    }
   }
 
   void setPassword(const string &password) {
-    d_webserverPassword = password;
+    if (!password.empty()) {
+      d_webserverPassword = make_unique<CredentialsHolder>(std::string(password));
+    }
+    else {
+      d_webserverPassword.reset();
+    }
   }
 
   void setMaxBodySize(ssize_t s) { // in megabytes
@@ -227,15 +241,15 @@ protected:
     return std::make_shared<Server>(d_listenaddress, d_port);
   }
 
+  void apiWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp, bool allowPassword);
+  void webWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp);
+
   string d_listenaddress;
   int d_port;
-  string d_password;
   std::shared_ptr<Server> d_server;
 
-  std::string d_apikey;
-  void apiWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp, bool allowPassword);
-  std::string d_webserverPassword;
-  void webWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp);
+  std::unique_ptr<CredentialsHolder> d_apikey{nullptr};
+  std::unique_ptr<CredentialsHolder> d_webserverPassword{nullptr};
 
   ssize_t d_maxbodysize; // in bytes