std::unique_ptr<CredentialsHolder> apiKey;
boost::optional<std::unordered_map<std::string, std::string> > customHeaders;
bool apiRequiresAuthentication{true};
+ bool dashboardRequiresAuthentication{true};
bool statsRequireAuthentication{true};
};
out << "None" << endl;
}
out << "API requires authentication: " << (config->apiRequiresAuthentication ? "yes" : "no") << endl;
+ out << "Dashboard requires authentication: " << (config->dashboardRequiresAuthentication ? "yes" : "no") << endl;
out << "Statistics require authentication: " << (config->statsRequireAuthentication ? "yes" : "no") << endl;
out << "Password: " << (config->password ? "set" : "unset") << endl;
out << "API key: " << (config->apiKey ? "set" : "unset") << endl;
return false;
}
-static bool checkWebPassword(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& password)
+static bool checkWebPassword(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& password, bool dashboardRequiresAuthentication)
{
+ if (!dashboardRequiresAuthentication) {
+ return true;
+ }
+
static const char basicStr[] = "basic ";
const auto header = req.headers.find("authorization");
if (isAStatsRequest(req)) {
if (config->statsRequireAuthentication) {
/* Access to the stats is allowed for both API and Web users */
- return checkAPIKey(req, config->apiKey) || checkWebPassword(req, config->password);
+ return checkAPIKey(req, config->apiKey) || checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
}
return true;
}
return true;
}
- return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password);
+ return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
}
- return checkWebPassword(req, config->password);
+ return checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
}
static bool isMethodAllowed(const YaHTTP::Request& req)
else if (!handleAuthorization(req)) {
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, conn.getClient().toStringWithPort());
+ vinfolog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, conn.getClient().toStringWithPort());
}
resp.status = 401;
resp.body = "<h1>Unauthorized</h1>";
g_webserverConfig.lock()->apiRequiresAuthentication = require;
}
+void setWebserverDashboardRequiresAuthentication(bool require)
+{
+ g_webserverConfig.lock()->dashboardRequiresAuthentication = require;
+}
+
void setWebserverMaxConcurrentConnections(size_t max)
{
s_connManager.setMaxConcurrentConnections(max);
void dnsdistWebserverThread(int sock, const ComboAddress& local)
{
setThreadName("dnsdist/webserv");
- warnlog("Webserver launched on %s", local.toStringWithPort());
+ infolog("Webserver launched on %s", local.toStringWithPort());
- if (!g_webserverConfig.lock()->password) {
- warnlog("Webserver launched on %s without a password set!", local.toStringWithPort());
+ {
+ auto config = g_webserverConfig.lock();
+ if (!config->password && config->dashboardRequiresAuthentication) {
+ warnlog("Webserver launched on %s without a password set!", local.toStringWithPort());
+ }
}
- for(;;) {
+ for (;;) {
try {
ComboAddress remote(local);
int fd = SAccept(sock, remote);
The optional ``password`` and ``apiKey`` parameters now accept hashed passwords.
The optional ``hashPlaintextCredentials`` parameter has been added.
- .. versionchanged:: 1.6.0
- ``apiRequiresAuthentication`` optional parameters added.
+ .. versionchanged:: 1.8.0
+ ``apiRequiresAuthentication``, ``dashboardRequiresAuthentication`` optional parameters added.
Setup webserver configuration. See :func:`webserver`.
* ``customHeaders={[str]=str,...}``: map of string - Allows setting custom headers and removing the defaults.
* ``acl=newACL``: string - List of IP addresses, as a string, that are allowed to open a connection to the web server. Defaults to "127.0.0.1, ::1".
* ``apiRequiresAuthentication``: bool - Whether access to the API (/api endpoints) require a valid API key. Defaults to true.
+ * ``dashbpardRequiresAuthentication``: bool - Whether access to the internal dashboard requires a valid password. Defaults to true.
* ``statsRequireAuthentication``: bool - Whether access to the statistics (/metrics and /jsonstat endpoints) require a valid password or API key. Defaults to true.
* ``maxConcurrentConnections``: int - The maximum number of concurrent web connections, or 0 which means an unlimited number. Defaults to 100.
* ``hashPlaintextCredentials``: bool - Whether passwords and API keys provided in plaintext should be hashed during startup, to prevent the plaintext versions from staying in memory. Doing so increases significantly the cost of verifying credentials. Defaults to false.
self.assertTrue(r)
self.assertEqual(r.status_code, 200)
+class TestDashboardWithoutAuthentication(APITestsBase):
+ __test__ = True
+ _basicPath = '/'
+ _config_params = ['_testServerPort', '_webServerPort']
+ _config_template = """
+ setACL({"127.0.0.1/32", "::1/128"})
+ newServer({address="127.0.0.1:%d"})
+ webserver("127.0.0.1:%d")
+ setWebserverConfig({ dashboardRequiresAuthentication=false })
+ """
+ _verboseMode=True
+
+ def testDashboard(self):
+ """
+ API: Dashboard do not require authentication
+ """
+
+ for path in [self._basicPath]:
+ url = 'http://127.0.0.1:' + str(self._webServerPort) + path
+
+ r = requests.get(url, timeout=self._webTimeout)
+ self.assertTrue(r)
+ self.assertEqual(r.status_code, 200)
+
class TestCustomLuaEndpoint(APITestsBase):
__test__ = True
_config_template = """