Loading data/config.yaml +1 −0 Original line number Diff line number Diff line Loading @@ -19,4 +19,5 @@ AUTHDB: PATH: "/tmp/cluster.db" ADMINDB: PATH: "/tmp/admin.db" ADMIN_KEY: "changeme-authdb-admin-key" VACUUM_INTERVAL: 6 src/admin.cpp +126 −2 Original line number Diff line number Diff line Loading @@ -146,6 +146,10 @@ namespace authdb { _DomainCacheEvictor = std::move(evictor); } void setAdminKey(const std::string &key) { _AdminKey = key; } /* ---- JSON API: List Users ---- */ void apiListUsers(libhttppp::HttpRequest &curreq, const char *cdid) { AuthBackend *backend = getDomain(cdid); Loading Loading @@ -805,7 +809,7 @@ namespace authdb { /* ---- JSON API: Create Domain ---- */ void apiCreateDomain(libhttppp::HttpRequest &curreq, const SessionData *sdat) { if (!isSuperAdmin(sdat)) if (sdat && !isSuperAdmin(sdat)) throw AuthBackendError("createDomain: nur SuperAdmins von admin.local erlaubt!"); std::vector<char> body; libhttppp::HttpForm curform; Loading Loading @@ -936,7 +940,7 @@ namespace authdb { /* ---- JSON API: Remove Domain ---- */ void apiRemoveDomain(libhttppp::HttpRequest &curreq, const char *cdid, const SessionData *sdat) { if (!isSuperAdmin(sdat)) if (sdat && !isSuperAdmin(sdat)) throw AuthBackendError("removeDomain: nur SuperAdmins von admin.local erlaubt!"); // Purge domain backend cluster data before removing the domain record Loading Loading @@ -1969,8 +1973,26 @@ namespace authdb { libhttppp::HttpCookie cookie; const SessionData *sdat = nullptr; uuid::uuid ssid; bool apiKeyAuth = false; // Check for API key authentication (Bearer token) if (!_AdminKey.empty()) { libhttppp::HttpHeader::HeaderData *authHeader = curreq.getHeaderData("authorization"); if (authHeader && authHeader->getfirstValue()) { std::string authVal = authHeader->getfirstValue()->getvalue(); std::string key; if (authVal.size() > 7 && authVal.substr(0, 7) == "Bearer ") key = authVal.substr(7); else if (authVal.size() > 7 && authVal.substr(0, 7) == "ApiKey ") key = authVal.substr(7); if (!key.empty() && key == _AdminKey) { apiKeyAuth = true; } } } try { if (!apiKeyAuth) { cookie.parse(curreq); for (libhttppp::HttpCookie::CookieData *cur = cookie.getfirstCookieData(); cur; cur = cur->nextCookieData()) { std::cerr << "[CTRL] cookie: key='" << cur->getKey() << "' val='" << cur->getValue() << "'" << std::endl; Loading Loading @@ -2006,6 +2028,7 @@ namespace authdb { if (!sdat) { throw AuthBackendError("No Session found!"); } } // end if (!apiKeyAuth) } catch (...) { // If cluster is critical (< k nodes), show warning page with auto-refresh if (g_Cluster && g_Cluster->isCritical()) { Loading Loading @@ -2089,6 +2112,102 @@ namespace authdb { // Check for /settings/{domain}/api/{action}[/{param}] if (sscanf(url.c_str(), "/settings/%[^/]/api/%[^/]/%s", domain, apiurl, param) >= 2) { try { // API key auth bypasses all permission checks (SuperAdmin) if (apiKeyAuth) { // Route API key requests without permission checks if (strcmp(apiurl, "listusers") == 0) { apiListUsers(curreq, domain); } else if (strcmp(apiurl, "getuser") == 0 && param[0]) { apiGetUser(curreq, domain, param); } else if (strcmp(apiurl, "createuser") == 0) { apiCreateUser(curreq, domain); } else if (strcmp(apiurl, "removeuser") == 0 && param[0]) { apiRemoveUser(curreq, domain, param); } else if (strcmp(apiurl, "edituser") == 0 && param[0]) { apiEditUser(curreq, domain, param); } else if (strcmp(apiurl, "listdomains") == 0) { apiListDomains(curreq); } else if (strcmp(apiurl, "checkpermissions") == 0) { // Return SuperAdmin permissions for API key json_object *jobj = json_object_new_object(); json_object_object_add(jobj, "superadmin", json_object_new_boolean(true)); json_object_object_add(jobj, "sessiondomain", json_object_new_string("admin")); json_object *jgpo = json_object_new_object(); for (size_t i = 0; gpo_default[i][0] != nullptr; ++i) json_object_object_add(jgpo, gpo_default[i][0], json_object_new_boolean(true)); json_object_object_add(jobj, "gpo", jgpo); json_object_object_add(jobj, "backendtype", json_object_new_int(_AdminBackend.getType())); json_object_object_add(jobj, "cluster", json_object_new_boolean(g_Cluster && g_Cluster->isRunning())); sendJson(curreq, jobj); json_object_put(jobj); } else if (strcmp(apiurl, "createdomain") == 0) { apiCreateDomain(curreq, nullptr); } else if (strcmp(apiurl, "removedomain") == 0 && param[0]) { apiRemoveDomain(curreq, param, nullptr); } else if (strcmp(apiurl, "listgroups") == 0) { apiListGroups(curreq, domain); } else if (strcmp(apiurl, "getgroup") == 0 && param[0]) { apiGetGroup(curreq, domain, param); } else if (strcmp(apiurl, "creategroup") == 0) { apiCreateGroup(curreq, domain); } else if (strcmp(apiurl, "removegroup") == 0 && param[0]) { apiRemoveGroup(curreq, domain, param); } else if (strcmp(apiurl, "editgroup") == 0 && param[0]) { apiEditGroup(curreq, domain, param); } else if (strcmp(apiurl, "listsessions") == 0) { apiListSessions(curreq, domain); } else if (strcmp(apiurl, "removesession") == 0 && param[0]) { apiRemoveSession(curreq, param); } else if (strcmp(apiurl, "reloadsession") == 0 && param[0]) { apiReloadSession(curreq, domain, param); } else if (strcmp(apiurl, "listgpos") == 0) { apiListGpos(curreq, domain); } else if (strcmp(apiurl, "getgpo") == 0 && param[0]) { apiGetGpo(curreq, domain, param); } else if (strcmp(apiurl, "creategpo") == 0) { apiCreateGpo(curreq, domain); } else if (strcmp(apiurl, "removegpo") == 0 && param[0]) { apiRemoveGpo(curreq, domain, param); } else if (strcmp(apiurl, "editgpo") == 0 && param[0]) { apiEditGpo(curreq, domain, param); } else if (strcmp(apiurl, "listclients") == 0) { apiListClients(curreq, domain); } else if (strcmp(apiurl, "getclient") == 0 && param[0]) { apiGetClient(curreq, domain, param); } else if (strcmp(apiurl, "createclient") == 0) { apiCreateClient(curreq, domain); } else if (strcmp(apiurl, "removeclient") == 0 && param[0]) { apiRemoveClient(curreq, domain, param); } else if (strcmp(apiurl, "editclient") == 0 && param[0]) { apiEditClient(curreq, domain, param); } else if (strcmp(apiurl, "listservices") == 0) { apiListServices(curreq, domain); } else if (strcmp(apiurl, "getservice") == 0 && param[0]) { apiGetService(curreq, domain, param); } else if (strcmp(apiurl, "createservice") == 0) { apiCreateService(curreq, domain); } else if (strcmp(apiurl, "removeservice") == 0 && param[0]) { apiRemoveService(curreq, domain, param); } else if (strcmp(apiurl, "editservice") == 0 && param[0]) { apiEditService(curreq, domain, param); } else if (strcmp(apiurl, "export") == 0) { apiExportDb(curreq, domain); } else if (strcmp(apiurl, "import") == 0) { apiImportDb(curreq, domain); } else if (strcmp(apiurl, "vacuum") == 0) { apiVacuum(curreq, domain); } else if (strcmp(apiurl, "clusterstatus") == 0) { apiClusterStatus(curreq); } else if (strcmp(apiurl, "clusterscrub") == 0) { apiClusterScrub(curreq); } else if (strcmp(apiurl, "clusterrebalance") == 0) { apiClusterRebalance(curreq); } else { sendJsonError(curreq, "Unknown API endpoint", 404); } return; } if (strcmp(apiurl, "listusers") == 0) { checkDomainAccess(sdat, domain); checkGPO(sdat, "a1b2c3d4-452e-11f0-b1cc-1a2b3c4d5e02"); Loading Loading @@ -2251,6 +2370,7 @@ namespace authdb { std::vector<char> _AdminHtml; std::function<AuthBackend*(const char*)> _DomainResolver; std::function<void(const char*)> _DomainCacheEvictor; std::string _AdminKey; }; AdminInterface::AdminInterface(AuthBackend &bck, std::vector<netplus::socket*> ssock) : _bck(bck) { Loading @@ -2269,6 +2389,10 @@ namespace authdb { _ctl->setDomainCacheEvictor(std::move(evictor)); } void AdminInterface::setAdminKey(const std::string &key) { _ctl->setAdminKey(key); } void AdminInterface::Request(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args) { if (curreq.getRequestURL().compare(0, 9, "/settings", 9) == 0) { _ctl->SettingsController(curreq, tid, args); Loading src/admin.h +1 −0 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ namespace authdb { void Request(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args); void setDomainResolver(std::function<AuthBackend*(const char*)> resolver); void setDomainCacheEvictor(std::function<void(const char*)> evictor); void setAdminKey(const std::string &key); public: AdminController * _ctl; AuthBackend &_bck; Loading src/authdb.cpp +14 −0 Original line number Diff line number Diff line Loading @@ -126,6 +126,10 @@ namespace authdb { }); }; void setAdminKey(const std::string &key) { _AdminInterface.setAdminKey(key); } void WizzardEvent(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args){ std::lock_guard<std::mutex> wlock(g_wizzard); Loading Loading @@ -806,6 +810,16 @@ int main(int argc,char *argv[]){ authdb::AuthDB authdb(backend,httpd.getServerSockets()); // Set admin API key if configured auto *adminKeyNode = config.getKey("/AUTHDB/ADMIN_KEY"); if (adminKeyNode) { std::string adminKey = config.getValue(adminKeyNode, 0); if (!adminKey.empty()) { authdb.setAdminKey(adminKey); std::cerr << "[AUTHDB] Admin API key authentication enabled" << std::endl; } } // Pre-load all domain backends into cache so API requests // don't need to lock the admin backend for domain resolution. if (backend.end() > sizeof(authdb::AuthHeader)) Loading Loading
data/config.yaml +1 −0 Original line number Diff line number Diff line Loading @@ -19,4 +19,5 @@ AUTHDB: PATH: "/tmp/cluster.db" ADMINDB: PATH: "/tmp/admin.db" ADMIN_KEY: "changeme-authdb-admin-key" VACUUM_INTERVAL: 6
src/admin.cpp +126 −2 Original line number Diff line number Diff line Loading @@ -146,6 +146,10 @@ namespace authdb { _DomainCacheEvictor = std::move(evictor); } void setAdminKey(const std::string &key) { _AdminKey = key; } /* ---- JSON API: List Users ---- */ void apiListUsers(libhttppp::HttpRequest &curreq, const char *cdid) { AuthBackend *backend = getDomain(cdid); Loading Loading @@ -805,7 +809,7 @@ namespace authdb { /* ---- JSON API: Create Domain ---- */ void apiCreateDomain(libhttppp::HttpRequest &curreq, const SessionData *sdat) { if (!isSuperAdmin(sdat)) if (sdat && !isSuperAdmin(sdat)) throw AuthBackendError("createDomain: nur SuperAdmins von admin.local erlaubt!"); std::vector<char> body; libhttppp::HttpForm curform; Loading Loading @@ -936,7 +940,7 @@ namespace authdb { /* ---- JSON API: Remove Domain ---- */ void apiRemoveDomain(libhttppp::HttpRequest &curreq, const char *cdid, const SessionData *sdat) { if (!isSuperAdmin(sdat)) if (sdat && !isSuperAdmin(sdat)) throw AuthBackendError("removeDomain: nur SuperAdmins von admin.local erlaubt!"); // Purge domain backend cluster data before removing the domain record Loading Loading @@ -1969,8 +1973,26 @@ namespace authdb { libhttppp::HttpCookie cookie; const SessionData *sdat = nullptr; uuid::uuid ssid; bool apiKeyAuth = false; // Check for API key authentication (Bearer token) if (!_AdminKey.empty()) { libhttppp::HttpHeader::HeaderData *authHeader = curreq.getHeaderData("authorization"); if (authHeader && authHeader->getfirstValue()) { std::string authVal = authHeader->getfirstValue()->getvalue(); std::string key; if (authVal.size() > 7 && authVal.substr(0, 7) == "Bearer ") key = authVal.substr(7); else if (authVal.size() > 7 && authVal.substr(0, 7) == "ApiKey ") key = authVal.substr(7); if (!key.empty() && key == _AdminKey) { apiKeyAuth = true; } } } try { if (!apiKeyAuth) { cookie.parse(curreq); for (libhttppp::HttpCookie::CookieData *cur = cookie.getfirstCookieData(); cur; cur = cur->nextCookieData()) { std::cerr << "[CTRL] cookie: key='" << cur->getKey() << "' val='" << cur->getValue() << "'" << std::endl; Loading Loading @@ -2006,6 +2028,7 @@ namespace authdb { if (!sdat) { throw AuthBackendError("No Session found!"); } } // end if (!apiKeyAuth) } catch (...) { // If cluster is critical (< k nodes), show warning page with auto-refresh if (g_Cluster && g_Cluster->isCritical()) { Loading Loading @@ -2089,6 +2112,102 @@ namespace authdb { // Check for /settings/{domain}/api/{action}[/{param}] if (sscanf(url.c_str(), "/settings/%[^/]/api/%[^/]/%s", domain, apiurl, param) >= 2) { try { // API key auth bypasses all permission checks (SuperAdmin) if (apiKeyAuth) { // Route API key requests without permission checks if (strcmp(apiurl, "listusers") == 0) { apiListUsers(curreq, domain); } else if (strcmp(apiurl, "getuser") == 0 && param[0]) { apiGetUser(curreq, domain, param); } else if (strcmp(apiurl, "createuser") == 0) { apiCreateUser(curreq, domain); } else if (strcmp(apiurl, "removeuser") == 0 && param[0]) { apiRemoveUser(curreq, domain, param); } else if (strcmp(apiurl, "edituser") == 0 && param[0]) { apiEditUser(curreq, domain, param); } else if (strcmp(apiurl, "listdomains") == 0) { apiListDomains(curreq); } else if (strcmp(apiurl, "checkpermissions") == 0) { // Return SuperAdmin permissions for API key json_object *jobj = json_object_new_object(); json_object_object_add(jobj, "superadmin", json_object_new_boolean(true)); json_object_object_add(jobj, "sessiondomain", json_object_new_string("admin")); json_object *jgpo = json_object_new_object(); for (size_t i = 0; gpo_default[i][0] != nullptr; ++i) json_object_object_add(jgpo, gpo_default[i][0], json_object_new_boolean(true)); json_object_object_add(jobj, "gpo", jgpo); json_object_object_add(jobj, "backendtype", json_object_new_int(_AdminBackend.getType())); json_object_object_add(jobj, "cluster", json_object_new_boolean(g_Cluster && g_Cluster->isRunning())); sendJson(curreq, jobj); json_object_put(jobj); } else if (strcmp(apiurl, "createdomain") == 0) { apiCreateDomain(curreq, nullptr); } else if (strcmp(apiurl, "removedomain") == 0 && param[0]) { apiRemoveDomain(curreq, param, nullptr); } else if (strcmp(apiurl, "listgroups") == 0) { apiListGroups(curreq, domain); } else if (strcmp(apiurl, "getgroup") == 0 && param[0]) { apiGetGroup(curreq, domain, param); } else if (strcmp(apiurl, "creategroup") == 0) { apiCreateGroup(curreq, domain); } else if (strcmp(apiurl, "removegroup") == 0 && param[0]) { apiRemoveGroup(curreq, domain, param); } else if (strcmp(apiurl, "editgroup") == 0 && param[0]) { apiEditGroup(curreq, domain, param); } else if (strcmp(apiurl, "listsessions") == 0) { apiListSessions(curreq, domain); } else if (strcmp(apiurl, "removesession") == 0 && param[0]) { apiRemoveSession(curreq, param); } else if (strcmp(apiurl, "reloadsession") == 0 && param[0]) { apiReloadSession(curreq, domain, param); } else if (strcmp(apiurl, "listgpos") == 0) { apiListGpos(curreq, domain); } else if (strcmp(apiurl, "getgpo") == 0 && param[0]) { apiGetGpo(curreq, domain, param); } else if (strcmp(apiurl, "creategpo") == 0) { apiCreateGpo(curreq, domain); } else if (strcmp(apiurl, "removegpo") == 0 && param[0]) { apiRemoveGpo(curreq, domain, param); } else if (strcmp(apiurl, "editgpo") == 0 && param[0]) { apiEditGpo(curreq, domain, param); } else if (strcmp(apiurl, "listclients") == 0) { apiListClients(curreq, domain); } else if (strcmp(apiurl, "getclient") == 0 && param[0]) { apiGetClient(curreq, domain, param); } else if (strcmp(apiurl, "createclient") == 0) { apiCreateClient(curreq, domain); } else if (strcmp(apiurl, "removeclient") == 0 && param[0]) { apiRemoveClient(curreq, domain, param); } else if (strcmp(apiurl, "editclient") == 0 && param[0]) { apiEditClient(curreq, domain, param); } else if (strcmp(apiurl, "listservices") == 0) { apiListServices(curreq, domain); } else if (strcmp(apiurl, "getservice") == 0 && param[0]) { apiGetService(curreq, domain, param); } else if (strcmp(apiurl, "createservice") == 0) { apiCreateService(curreq, domain); } else if (strcmp(apiurl, "removeservice") == 0 && param[0]) { apiRemoveService(curreq, domain, param); } else if (strcmp(apiurl, "editservice") == 0 && param[0]) { apiEditService(curreq, domain, param); } else if (strcmp(apiurl, "export") == 0) { apiExportDb(curreq, domain); } else if (strcmp(apiurl, "import") == 0) { apiImportDb(curreq, domain); } else if (strcmp(apiurl, "vacuum") == 0) { apiVacuum(curreq, domain); } else if (strcmp(apiurl, "clusterstatus") == 0) { apiClusterStatus(curreq); } else if (strcmp(apiurl, "clusterscrub") == 0) { apiClusterScrub(curreq); } else if (strcmp(apiurl, "clusterrebalance") == 0) { apiClusterRebalance(curreq); } else { sendJsonError(curreq, "Unknown API endpoint", 404); } return; } if (strcmp(apiurl, "listusers") == 0) { checkDomainAccess(sdat, domain); checkGPO(sdat, "a1b2c3d4-452e-11f0-b1cc-1a2b3c4d5e02"); Loading Loading @@ -2251,6 +2370,7 @@ namespace authdb { std::vector<char> _AdminHtml; std::function<AuthBackend*(const char*)> _DomainResolver; std::function<void(const char*)> _DomainCacheEvictor; std::string _AdminKey; }; AdminInterface::AdminInterface(AuthBackend &bck, std::vector<netplus::socket*> ssock) : _bck(bck) { Loading @@ -2269,6 +2389,10 @@ namespace authdb { _ctl->setDomainCacheEvictor(std::move(evictor)); } void AdminInterface::setAdminKey(const std::string &key) { _ctl->setAdminKey(key); } void AdminInterface::Request(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args) { if (curreq.getRequestURL().compare(0, 9, "/settings", 9) == 0) { _ctl->SettingsController(curreq, tid, args); Loading
src/admin.h +1 −0 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ namespace authdb { void Request(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args); void setDomainResolver(std::function<AuthBackend*(const char*)> resolver); void setDomainCacheEvictor(std::function<void(const char*)> evictor); void setAdminKey(const std::string &key); public: AdminController * _ctl; AuthBackend &_bck; Loading
src/authdb.cpp +14 −0 Original line number Diff line number Diff line Loading @@ -126,6 +126,10 @@ namespace authdb { }); }; void setAdminKey(const std::string &key) { _AdminInterface.setAdminKey(key); } void WizzardEvent(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args){ std::lock_guard<std::mutex> wlock(g_wizzard); Loading Loading @@ -806,6 +810,16 @@ int main(int argc,char *argv[]){ authdb::AuthDB authdb(backend,httpd.getServerSockets()); // Set admin API key if configured auto *adminKeyNode = config.getKey("/AUTHDB/ADMIN_KEY"); if (adminKeyNode) { std::string adminKey = config.getValue(adminKeyNode, 0); if (!adminKey.empty()) { authdb.setAdminKey(adminKey); std::cerr << "[AUTHDB] Admin API key authentication enabled" << std::endl; } } // Pre-load all domain backends into cache so API requests // don't need to lock the admin backend for domain resolution. if (backend.end() > sizeof(authdb::AuthHeader)) Loading