use httplib for controller endpoints

This commit is contained in:
Grant Limberg 2023-04-27 12:05:56 -07:00
parent 4d6151c923
commit 412172cdf1
No known key found for this signature in database
GPG key ID: 8F2F97D3BE8D7735
3 changed files with 530 additions and 0 deletions

View file

@ -16,6 +16,7 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <type_traits>
#ifndef _WIN32
#include <sys/time.h>
@ -553,6 +554,528 @@ void EmbeddedNetworkController::request(
_queue.post(qe);
}
void EmbeddedNetworkController::configureHTTPControlPlane(
httplib::Server &s,
const std::function<void(const httplib::Request&, httplib::Response&, std::string)> setContent)
{
s.Get("/controller/network", [&](const httplib::Request &req, httplib::Response &res) {
std::set<uint64_t> networkIds;
_db.networks(networkIds);
char tmp[64];
auto out = json::array();
for(std::set<uint64_t>::const_iterator i(networkIds.begin()); i != networkIds.end(); ++i) {
OSUtils::ztsnprintf(tmp, sizeof(tmp), "%.16llx", *i);
out.push_back(tmp);
}
setContent(req, res, out.dump());
});
s.Get("/controller/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) {
auto networkID = req.matches[1];
uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str());
json network;
if (!_db.get(nwid, network)) {
res.status = 404;
return;
}
setContent(req, res, network.dump());
});
auto _networkUpdateFromPostData = [&](uint64_t networkID, const std::string &body) -> std::string {
json b = OSUtils::jsonParse(body);
char nwids[24];
OSUtils::ztsnprintf(nwids, sizeof(nwids), "%.16llx", networkID);
json network;
_db.get(networkID, network);
DB::initNetwork(network);
if (b.count("name")) network["name"] = OSUtils::jsonString(b["name"],"");
if (b.count("private")) network["private"] = OSUtils::jsonBool(b["private"],true);
if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false);
if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL);
if (b.count("mtu")) network["mtu"] = std::max(std::min((unsigned int)OSUtils::jsonInt(b["mtu"],ZT_DEFAULT_MTU),(unsigned int)ZT_MAX_MTU),(unsigned int)ZT_MIN_MTU);
if (b.count("remoteTraceTarget")) {
const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],""));
if (rtt.length() == 10) {
network["remoteTraceTarget"] = rtt;
} else {
network["remoteTraceTarget"] = json();
}
}
if (b.count("remoteTraceLevel")) network["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL);
if (b.count("v4AssignMode")) {
json nv4m;
json &v4m = b["v4AssignMode"];
if (v4m.is_string()) { // backward compatibility
nv4m["zt"] = (OSUtils::jsonString(v4m,"") == "zt");
} else if (v4m.is_object()) {
nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false);
} else nv4m["zt"] = false;
network["v4AssignMode"] = nv4m;
}
if (b.count("v6AssignMode")) {
json nv6m;
json &v6m = b["v6AssignMode"];
if (!nv6m.is_object()) nv6m = json::object();
if (v6m.is_string()) { // backward compatibility
std::vector<std::string> v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","",""));
std::sort(v6ms.begin(),v6ms.end());
v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end());
nv6m["rfc4193"] = false;
nv6m["zt"] = false;
nv6m["6plane"] = false;
for(std::vector<std::string>::iterator i(v6ms.begin());i!=v6ms.end();++i) {
if (*i == "rfc4193")
nv6m["rfc4193"] = true;
else if (*i == "zt")
nv6m["zt"] = true;
else if (*i == "6plane")
nv6m["6plane"] = true;
}
} else if (v6m.is_object()) {
if (v6m.count("rfc4193")) nv6m["rfc4193"] = OSUtils::jsonBool(v6m["rfc4193"],false);
if (v6m.count("zt")) nv6m["zt"] = OSUtils::jsonBool(v6m["zt"],false);
if (v6m.count("6plane")) nv6m["6plane"] = OSUtils::jsonBool(v6m["6plane"],false);
} else {
nv6m["rfc4193"] = false;
nv6m["zt"] = false;
nv6m["6plane"] = false;
}
network["v6AssignMode"] = nv6m;
}
if (b.count("routes")) {
json &rts = b["routes"];
if (rts.is_array()) {
json nrts = json::array();
for(unsigned long i=0;i<rts.size();++i) {
json &rt = rts[i];
if (rt.is_object()) {
json &target = rt["target"];
json &via = rt["via"];
if (target.is_string()) {
InetAddress t(target.get<std::string>().c_str());
InetAddress v;
if (via.is_string()) v.fromString(via.get<std::string>().c_str());
if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) {
json tmp;
char tmp2[64];
tmp["target"] = t.toString(tmp2);
if (v.ss_family == t.ss_family)
tmp["via"] = v.toIpString(tmp2);
else tmp["via"] = json();
nrts.push_back(tmp);
if (nrts.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
break;
}
}
}
}
network["routes"] = nrts;
}
}
if (b.count("ipAssignmentPools")) {
json &ipp = b["ipAssignmentPools"];
if (ipp.is_array()) {
json nipp = json::array();
for(unsigned long i=0;i<ipp.size();++i) {
json &ip = ipp[i];
if ((ip.is_object())&&(ip.count("ipRangeStart"))&&(ip.count("ipRangeEnd"))) {
InetAddress f(OSUtils::jsonString(ip["ipRangeStart"],"").c_str());
InetAddress t(OSUtils::jsonString(ip["ipRangeEnd"],"").c_str());
if ( ((f.ss_family == AF_INET)||(f.ss_family == AF_INET6)) && (f.ss_family == t.ss_family) ) {
json tmp = json::object();
char tmp2[64];
tmp["ipRangeStart"] = f.toIpString(tmp2);
tmp["ipRangeEnd"] = t.toIpString(tmp2);
nipp.push_back(tmp);
if (nipp.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
break;
}
}
}
network["ipAssignmentPools"] = nipp;
}
}
if (b.count("rules")) {
json &rules = b["rules"];
if (rules.is_array()) {
json nrules = json::array();
for(unsigned long i=0;i<rules.size();++i) {
json &rule = rules[i];
if (rule.is_object()) {
ZT_VirtualNetworkRule ztr;
if (_parseRule(rule,ztr)) {
nrules.push_back(_renderRule(ztr));
if (nrules.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
break;
}
}
}
network["rules"] = nrules;
}
}
if (b.count("authTokens")) {
json &authTokens = b["authTokens"];
if (authTokens.is_object()) {
json nat;
for(json::iterator t(authTokens.begin());t!=authTokens.end();++t) {
if ((t.value().is_number())&&(t.value() >= 0))
nat[t.key()] = t.value();
}
network["authTokens"] = nat;
} else {
network["authTokens"] = {{}};
}
}
if (b.count("capabilities")) {
json &capabilities = b["capabilities"];
if (capabilities.is_array()) {
std::map< uint64_t,json > ncaps;
for(unsigned long i=0;i<capabilities.size();++i) {
json &cap = capabilities[i];
if (cap.is_object()) {
json ncap = json::object();
const uint64_t capId = OSUtils::jsonInt(cap["id"],0ULL);
ncap["id"] = capId;
ncap["default"] = OSUtils::jsonBool(cap["default"],false);
json &rules = cap["rules"];
json nrules = json::array();
if (rules.is_array()) {
for(unsigned long i=0;i<rules.size();++i) {
json &rule = rules[i];
if (rule.is_object()) {
ZT_VirtualNetworkRule ztr;
if (_parseRule(rule,ztr)) {
nrules.push_back(_renderRule(ztr));
if (nrules.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
break;
}
}
}
}
ncap["rules"] = nrules;
ncaps[capId] = ncap;
}
}
json ncapsa = json::array();
for(std::map< uint64_t,json >::iterator c(ncaps.begin());c!=ncaps.end();++c) {
ncapsa.push_back(c->second);
if (ncapsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
break;
}
network["capabilities"] = ncapsa;
}
}
if (b.count("tags")) {
json &tags = b["tags"];
if (tags.is_array()) {
std::map< uint64_t,json > ntags;
for(unsigned long i=0;i<tags.size();++i) {
json &tag = tags[i];
if (tag.is_object()) {
json ntag = json::object();
const uint64_t tagId = OSUtils::jsonInt(tag["id"],0ULL);
ntag["id"] = tagId;
json &dfl = tag["default"];
if (dfl.is_null())
ntag["default"] = dfl;
else ntag["default"] = OSUtils::jsonInt(dfl,0ULL);
ntags[tagId] = ntag;
}
}
json ntagsa = json::array();
for(std::map< uint64_t,json >::iterator t(ntags.begin());t!=ntags.end();++t) {
ntagsa.push_back(t->second);
if (ntagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
break;
}
network["tags"] = ntagsa;
}
}
if (b.count("dns")) {
json &dns = b["dns"];
if (dns.is_object()) {
json nd;
nd["domain"] = dns["domain"];
json &srv = dns["servers"];
if (srv.is_array()) {
json ns = json::array();
for(unsigned int i=0;i<srv.size();++i) {
ns.push_back(srv[i]);
}
nd["servers"] = ns;
}
network["dns"] = nd;
}
}
network["id"] = nwids;
network["nwid"] = nwids;
DB::cleanNetwork(network);
_db.save(network, true);
return network.dump();
};
auto createNewNetwork = [&](const httplib::Request &req, httplib::Response &res) {
uint64_t nwid = 0;
uint64_t nwidPrefix = (Utils::hexStrToU64(_signingIdAddressString.c_str()) << 24) & 0xffffffffff000000ULL;
uint64_t nwidPostfix = 0;
for(unsigned long k=0;k<100000;++k) { // sanity limit on trials
Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix));
uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL);
if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL;
if (!_db.hasNetwork(tryNwid)) {
nwid = tryNwid;
break;
}
}
if (!nwid) {
res.status = 503;
return;
}
setContent(req, res, _networkUpdateFromPostData(nwid, req.body));
};
s.Put("/controller/network", createNewNetwork);
s.Post("/controller/network", createNewNetwork);
auto createNewNetworkOldAndBusted = [&](const httplib::Request &req, httplib::Response &res) {
auto inID = req.matches[1].str();
if (inID != _signingIdAddressString) {
res.status = 400;
return;
}
uint64_t nwid = 0;
uint64_t nwidPrefix = (Utils::hexStrToU64(inID.c_str()) << 24) & 0xffffffffff000000ULL;
uint64_t nwidPostfix = 0;
for(unsigned long k=0;k<100000;++k) { // sanity limit on trials
Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix));
uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL);
if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL;
if (!_db.hasNetwork(tryNwid)) {
nwid = tryNwid;
break;
}
}
if (!nwid) {
res.status = 503;
return;
}
setContent(req, res, _networkUpdateFromPostData(nwid, req.body));
};
s.Put("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted);
s.Post("/controller/network/([0-9a-fA-F]{10})______", createNewNetworkOldAndBusted);
s.Delete("/controller/network/([0-9a-fA-F]{16})", [&](const httplib::Request &req, httplib::Response &res) {
auto networkID = req.matches[1].str();
uint64_t nwid = Utils::hexStrToU64(networkID.c_str());
json network;
if (!_db.get(nwid,network)) {
res.status = 404;
return;
}
_db.eraseNetwork(nwid);
setContent(req, res, network.dump());
});
s.Get("/controller/network/([0-9a-fA-F]{16})/member", [&](const httplib::Request &req, httplib::Response &res) {
auto networkID = req.matches[1];
uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str());
json network;
if (!_db.get(nwid, network)) {
res.status = 404;
return;
}
json out = json::array();
std::vector<json> memTmp;
if (_db.get(nwid, network, memTmp)) {
for (auto m = memTmp.begin(); m != memTmp.end(); ++m) {
int revision = OSUtils::jsonInt((*m)["revsision"], 0);
std::string id = OSUtils::jsonString((*m)["id"], "");
if (id.length() == 10) {
json tmp = json::object();
tmp[id] = revision;
out.push_back(tmp);
}
}
}
setContent(req, res, out.dump());
});
s.Get("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) {
auto networkID = req.matches[1];
auto memberID = req.matches[2];
uint64_t nwid = Utils::hexStrToU64(networkID.str().c_str());
uint64_t memid = Utils::hexStrToU64(memberID.str().c_str());
json network;
json member;
if (!_db.get(nwid, network, memid, member)) {
res.status = 404;
return;
}
setContent(req, res, member.dump());
});
auto memberPost = [&](const httplib::Request &req, httplib::Response &res) {
auto networkID = req.matches[1].str();
auto memberID = req.matches[2].str();
uint64_t nwid = Utils::hexStrToU64(networkID.c_str());
uint64_t memid = Utils::hexStrToU64(memberID.c_str());
json network;
json member;
_db.get(nwid, network, memid, member);
DB::initMember(member);
json b = OSUtils::jsonParse(req.body);
if (b.count("activeBridge")) member["activeBridge"] = OSUtils::jsonBool(b["activeBridge"], false);
if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = OSUtils::jsonBool(b["noAutoAssignIps"], false);
if (b.count("authenticationExpiryTime")) member["authenticationExpiryTime"] = (uint64_t)OSUtils::jsonInt(b["authenticationExpiryTime"], 0ULL);
if (b.count("authenticationURL")) member["authenticationURL"] = OSUtils::jsonString(b["authenticationURL"], "");
if (b.count("remoteTraceTarget")) {
const std::string rtt(OSUtils::jsonString(b["remoteTraceTarget"],""));
if (rtt.length() == 10) {
member["remoteTraceTarget"] = rtt;
} else {
member["remoteTraceTarget"] = json();
}
}
if (b.count("remoteTraceLevel")) member["remoteTraceLevel"] = OSUtils::jsonInt(b["remoteTraceLevel"],0ULL);
if (b.count("authorized")) {
const bool newAuth = OSUtils::jsonBool(b["authorized"],false);
if (newAuth != OSUtils::jsonBool(member["authorized"],false)) {
member["authorized"] = newAuth;
member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = OSUtils::now();
if (newAuth) {
member["lastAuthorizedCredentialType"] = "api";
member["lastAuthorizedCredential"] = json();
}
}
}
if (b.count("ipAssignments")) {
json &ipa = b["ipAssignments"];
if (ipa.is_array()) {
json mipa(json::array());
for(unsigned long i=0;i<ipa.size();++i) {
std::string ips = ipa[i];
InetAddress ip(ips.c_str());
if ((ip.ss_family == AF_INET)||(ip.ss_family == AF_INET6)) {
char tmpip[64];
mipa.push_back(ip.toIpString(tmpip));
if (mipa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
break;
}
}
member["ipAssignments"] = mipa;
}
}
if (b.count("tags")) {
json &tags = b["tags"];
if (tags.is_array()) {
std::map<uint64_t,uint64_t> mtags;
for(unsigned long i=0;i<tags.size();++i) {
json &tag = tags[i];
if ((tag.is_array())&&(tag.size() == 2))
mtags[OSUtils::jsonInt(tag[0],0ULL) & 0xffffffffULL] = OSUtils::jsonInt(tag[1],0ULL) & 0xffffffffULL;
}
json mtagsa = json::array();
for(std::map<uint64_t,uint64_t>::iterator t(mtags.begin());t!=mtags.end();++t) {
json ta = json::array();
ta.push_back(t->first);
ta.push_back(t->second);
mtagsa.push_back(ta);
if (mtagsa.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
break;
}
member["tags"] = mtagsa;
}
}
if (b.count("capabilities")) {
json &capabilities = b["capabilities"];
if (capabilities.is_array()) {
json mcaps = json::array();
for(unsigned long i=0;i<capabilities.size();++i) {
mcaps.push_back(OSUtils::jsonInt(capabilities[i],0ULL));
if (mcaps.size() >= ZT_CONTROLLER_MAX_ARRAY_SIZE)
break;
}
std::sort(mcaps.begin(),mcaps.end());
mcaps.erase(std::unique(mcaps.begin(),mcaps.end()),mcaps.end());
member["capabilities"] = mcaps;
}
}
member["id"] = memberID;
member["address"] = memberID;
member["nwid"] = networkID;
DB::cleanMember(member);
_db.save(member, true);
setContent(req, res, member.dump());
};
s.Put("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost);
s.Post("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", memberPost);
s.Delete("/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})", [&](const httplib::Request &req, httplib::Response &res) {
auto networkID = req.matches[1].str();
auto memberID = req.matches[2].str();
uint64_t nwid = Utils::hexStrToU64(networkID.c_str());
uint64_t address = Utils::hexStrToU64(memberID.c_str());
json network, member;
if (!_db.get(nwid, network, address, member)) {
res.status = 404;
return;
}
if (!member.size()) {
res.status = 404;
return;
}
_db.eraseMember(nwid, address);
setContent(req, res, member.dump());
});
}
unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET(
const std::vector<std::string> &path,
const std::map<std::string,std::string> &urlArgs,

View file

@ -37,6 +37,8 @@
#include <nlohmann/json.hpp>
#include <cpp-httplib/httplib.h>
#include "DB.hpp"
#include "DBMirrorSet.hpp"
@ -66,6 +68,10 @@ public:
const Identity &identity,
const Dictionary<ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY> &metaData);
void configureHTTPControlPlane(
httplib::Server &s,
const std::function<void(const httplib::Request&, httplib::Response&, std::string)>);
unsigned int handleControlPlaneHttpGET(
const std::vector<std::string> &path,
const std::map<std::string,std::string> &urlArgs,

View file

@ -1985,6 +1985,7 @@ public:
if (_controller) {
// TODO: Wire up controller
_controller->configureHTTPControlPlane(_controlPlane, setContent);
}
_controlPlane.set_pre_routing_handler(authCheck);