diff options
-rw-r--r-- | service/OneService.cpp | 262 |
1 files changed, 224 insertions, 38 deletions
diff --git a/service/OneService.cpp b/service/OneService.cpp index 672e3799..7013d9dc 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -81,6 +81,12 @@ #include "../ext/http-parser/http_parser.h" #endif +#if ZT_VAULT_SUPPORT +extern "C" { +#include <curl/curl.h> +} +#endif + #include "../ext/json/json.hpp" using json = nlohmann::json; @@ -158,6 +164,14 @@ namespace ZeroTier { typedef BSDEthernetTap EthernetTap; } // TCP activity timeout #define ZT_TCP_ACTIVITY_TIMEOUT 60000 +#if ZT_VAULT_SUPPORT +size_t curlResponseWrite(void *ptr, size_t size, size_t nmemb, std::string *data) +{ + data->append((char*)ptr, size * nmemb); + return size * nmemb; +} +#endif + namespace ZeroTier { namespace { @@ -478,10 +492,12 @@ public: #endif // HashiCorp Vault Settings +#if ZT_VAULT_SUPPORT bool _vaultEnabled; std::string _vaultURL; std::string _vaultToken; std::string _vaultPath; // defaults to cubbyhole/zerotier/identity.secret for per-access key storage +#endif // Set to false to force service to stop volatile bool _run; @@ -518,12 +534,16 @@ public: ,_vaultEnabled(false) ,_vaultURL() ,_vaultToken() - ,_vaultPath("cubbyhole/zerotier/identity.secret") + ,_vaultPath("cubbyhole/zerotier") ,_run(true) { _ports[0] = 0; _ports[1] = 0; _ports[2] = 0; + +#if ZT_VAULT_SUPPORT + curl_global_init(CURL_GLOBAL_DEFAULT); +#endif } virtual ~OneServiceImpl() @@ -560,20 +580,6 @@ public: _authToken = _trimString(_authToken); } - { - struct ZT_Node_Callbacks cb; - cb.version = 0; - cb.stateGetFunction = SnodeStateGetFunction; - cb.statePutFunction = SnodeStatePutFunction; - cb.wirePacketSendFunction = SnodeWirePacketSendFunction; - cb.virtualNetworkFrameFunction = SnodeVirtualNetworkFrameFunction; - cb.virtualNetworkConfigFunction = SnodeVirtualNetworkConfigFunction; - cb.eventCallback = SnodeEventCallback; - cb.pathCheckFunction = SnodePathCheckFunction; - cb.pathLookupFunction = SnodePathLookupFunction; - _node = new Node(this,(void *)0,&cb,OSUtils::now()); - } - // Read local configuration std::vector<InetAddress> explicitBind; { @@ -663,14 +669,25 @@ public: for(std::map<InetAddress,ZT_PhysicalPathConfiguration>::iterator i(ppc.begin());i!=ppc.end();++i) _node->setPhysicalPathConfiguration(reinterpret_cast<const struct sockaddr_storage *>(&(i->first)),&(i->second)); } - - json &vaultConfig = _localConfig["vault"]; - } // Apply other runtime configuration from local.conf applyLocalConfig(); + { + struct ZT_Node_Callbacks cb; + cb.version = 0; + cb.stateGetFunction = SnodeStateGetFunction; + cb.statePutFunction = SnodeStatePutFunction; + cb.wirePacketSendFunction = SnodeWirePacketSendFunction; + cb.virtualNetworkFrameFunction = SnodeVirtualNetworkFrameFunction; + cb.virtualNetworkConfigFunction = SnodeVirtualNetworkConfigFunction; + cb.eventCallback = SnodeEventCallback; + cb.pathCheckFunction = SnodePathCheckFunction; + cb.pathLookupFunction = SnodePathLookupFunction; + _node = new Node(this, (void *)0, &cb, OSUtils::now()); + } + // Make sure we can use the primary port, and hunt for one if configured to do so const int portTrials = (_primaryPort == 0) ? 256 : 1; // if port is 0, pick random for(int k=0;k<portTrials;++k) { @@ -1524,22 +1541,43 @@ public: } } - json &vault = settings["valut"]; + json &vault = settings["vault"]; if (vault.is_object()) { const std::string url(OSUtils::jsonString(vault["vaultURL"], "").c_str()); - if (!url.empty()) + if (!url.empty()) { _vaultURL = url; + } const std::string token(OSUtils::jsonString(vault["vaultToken"], "").c_str()); - if (!token.empty()) + if (!token.empty()) { _vaultToken = token; + } const std::string path(OSUtils::jsonString(vault["vaultPath"], "").c_str()); - if (!path.empty()) + if (!path.empty()) { _vaultPath = path; + } + } - if (!_vaultURL.empty() && !_vaultToken.empty()) - _vaultEnabled = true; + // also check environment variables for values. Environment variables + // will override local.conf variables + const std::string envURL(getenv("VAULT_ADDR")); + if (!envURL.empty()) { + _vaultURL = envURL; + } + + const std::string envToken(getenv("VAULT_TOKEN")); + if (!envToken.empty()) { + _vaultToken = envToken; + } + + const std::string envPath(getenv("VAULT_PATH")); + if (!envPath.empty()) { + _vaultPath = envPath; + } + + if (!_vaultURL.empty() && !_vaultToken.empty()) { + _vaultEnabled = true; } } @@ -2109,17 +2147,88 @@ public: } } - inline void nodeVaultPutIdentitySecret(const void *data, int len) +#if ZT_VAULT_SUPPORT + inline bool nodeVaultPutIdentity(enum ZT_StateObjectType type, const void *data, int len) { - return; + bool retval = false; + if (type != ZT_STATE_OBJECT_IDENTITY_PUBLIC && type != ZT_STATE_OBJECT_IDENTITY_SECRET) { + return retval; + } + + CURL *curl = curl_easy_init(); + if (curl) { + char token[512] = { 0 }; + snprintf(token, sizeof(token), "X-Vault-Token: %s", _vaultToken.c_str()); + + struct curl_slist *chunk = NULL; + chunk = curl_slist_append(chunk, token); + + + char content_type[512] = { 0 }; + snprintf(content_type, sizeof(content_type), "Content-Type: application/json"); + + chunk = curl_slist_append(chunk, content_type); + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); + + char url[2048] = { 0 }; + snprintf(url, sizeof(url), "%s/v1/%s", _vaultURL.c_str(), _vaultPath.c_str()); + + curl_easy_setopt(curl, CURLOPT_URL, url); + + json d = json::object(); + if (type == ZT_STATE_OBJECT_IDENTITY_PUBLIC) { + std::string key((const char*)data, len); + d["public"] = key; + } + else if (type == ZT_STATE_OBJECT_IDENTITY_SECRET) { + std::string key((const char*)data, len); + d["secret"] = key; + } + + if (!d.empty()) { + std::string post = d.dump(); + + if (!post.empty()) { + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post.length()); + +#ifndef NDEBUG + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); +#endif + + CURLcode res = curl_easy_perform(curl); + if (res == CURLE_OK) { + long response_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + if (response_code == 200 || response_code == 204) { + retval = true; + } + } + } + } + + curl_easy_cleanup(curl); + curl = NULL; + curl_slist_free_all(chunk); + chunk = NULL; + } + + return retval; } +#endif inline void nodeStatePutFunction(enum ZT_StateObjectType type,const uint64_t id[2],const void *data,int len) { - if (_vaultEnabled && type == ZT_STATE_OBJECT_IDENTITY_SECRET) { - nodeVaultPutIdentitySecret(data, len); - return; +#if ZT_VAULT_SUPPORT + if (_vaultEnabled && (type == ZT_STATE_OBJECT_IDENTITY_SECRET || type == ZT_STATE_OBJECT_IDENTITY_PUBLIC)) { + if (nodeVaultPutIdentity(type, data, len)) { + // value successfully written to Vault + return; + } + // else fallback to disk } +#endif char p[1024]; FILE *f; @@ -2186,21 +2295,96 @@ public: OSUtils::rm(p); } } - - inline int nodeVaultGetIdentitySecret(void *data, unsigned int maxlen) + +#if ZT_VAULT_SUPPORT + inline int nodeVaultGetIdentity(enum ZT_StateObjectType type, void *data, unsigned int maxlen) { - return 0; + if (type != ZT_STATE_OBJECT_IDENTITY_SECRET && type != ZT_STATE_OBJECT_IDENTITY_PUBLIC) { + return -1; + } + + int ret = -1; + CURL *curl = curl_easy_init(); + if (curl) { + char token[512] = { 0 }; + snprintf(token, sizeof(token), "X-Vault-Token: %s", _vaultToken.c_str()); + + struct curl_slist *chunk = NULL; + chunk = curl_slist_append(chunk, token); + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); + + char url[2048] = { 0 }; + snprintf(url, sizeof(url), "%s/v1/%s", _vaultURL.c_str(), _vaultPath.c_str()); + + curl_easy_setopt(curl, CURLOPT_URL, url); + + std::string response; + std::string res_headers; + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curlResponseWrite); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &res_headers); + +#ifndef NDEBUG + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); +#endif + + CURLcode res = curl_easy_perform(curl); + + if (res == CURLE_OK) { + long response_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + if (response_code == 200) { + + try { + json payload = json::parse(response); + if (!payload["data"].is_null()) { + json &d = payload["data"]; + if (type == ZT_STATE_OBJECT_IDENTITY_SECRET) { + std::string secret = OSUtils::jsonString(d["secret"],""); + + if (!secret.empty()) { + ret = (int)secret.length(); + memcpy(data, secret.c_str(), ret); + } + } + else if (type == ZT_STATE_OBJECT_IDENTITY_PUBLIC) { + std::string pub = OSUtils::jsonString(d["public"],""); + + if (!pub.empty()) { + ret = (int)pub.length(); + memcpy(data, pub.c_str(), ret); + } + } + } + } + catch (...) { + ret = -1; + } + } + } + + curl_easy_cleanup(curl); + curl = NULL; + curl_slist_free_all(chunk); + chunk = NULL; + } + return ret; } +#endif inline int nodeStateGetFunction(enum ZT_StateObjectType type,const uint64_t id[2],void *data,unsigned int maxlen) { - if (_vaultEnabled && type == ZT_STATE_OBJECT_IDENTITY_SECRET) { - int retval = nodeVaultGetIdentitySecret(data, maxlen); +#if ZT_VAULT_SUPPORT + if (_vaultEnabled && (type == ZT_STATE_OBJECT_IDENTITY_SECRET || type == ZT_STATE_OBJECT_IDENTITY_PUBLIC) ) { + int retval = nodeVaultGetIdentity(type, data, maxlen); if (retval >= 0) return retval; // else continue file based lookup } +#endif char p[4096]; switch(type) { @@ -2229,15 +2413,17 @@ public: if (f) { int n = (int)fread(data,1,maxlen,f); fclose(f); - - if (_vaultEnabled && type == ZT_STATE_OBJECT_IDENTITY_SECRET) { +#if ZT_VAULT_SUPPORT + if (_vaultEnabled && (type == ZT_STATE_OBJECT_IDENTITY_SECRET || type == ZT_STATE_OBJECT_IDENTITY_PUBLIC)) { // If we've gotten here while Vault is enabled, Vault does not know the key and it's been // read from disk instead. // // We should put the value in Vault and remove the local file. - nodeVaultPutIdentitySecret(data, n); - unlink(p); + if (nodeVaultPutIdentity(type, data, n)) { + unlink(p); + } } +#endif if (n >= 0) return n; } |