summaryrefslogtreecommitdiff
path: root/service/OneService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'service/OneService.cpp')
-rw-r--r--service/OneService.cpp285
1 files changed, 271 insertions, 14 deletions
diff --git a/service/OneService.cpp b/service/OneService.cpp
index 1d271b21..e7229717 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;
@@ -162,6 +168,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 {
@@ -481,6 +495,14 @@ public:
PortMapper *_portMapper;
#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;
Mutex _run_m;
@@ -513,11 +535,21 @@ public:
#ifdef ZT_USE_MINIUPNPC
,_portMapper((PortMapper *)0)
#endif
+#ifdef ZT_VAULT_SUPPORT
+ ,_vaultEnabled(false)
+ ,_vaultURL()
+ ,_vaultToken()
+ ,_vaultPath("cubbyhole/zerotier")
+#endif
,_run(true)
{
_ports[0] = 0;
_ports[1] = 0;
_ports[2] = 0;
+
+#if ZT_VAULT_SUPPORT
+ curl_global_init(CURL_GLOBAL_DEFAULT);
+#endif
}
virtual ~OneServiceImpl()
@@ -525,6 +557,10 @@ public:
_binder.closeAll(_phy);
_phy.close(_localControlSocket4);
_phy.close(_localControlSocket6);
+#if ZT_VAULT_SUPPORT
+ curl_global_cleanup();
+#endif
+
#ifdef ZT_USE_MINIUPNPC
delete _portMapper;
#endif
@@ -554,20 +590,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;
{
@@ -662,6 +684,20 @@ public:
// 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) {
@@ -1514,6 +1550,47 @@ public:
_allowManagementFrom.push_back(nw);
}
}
+
+#if ZT_VAULT_SUPPORT
+ json &vault = settings["vault"];
+ if (vault.is_object()) {
+ const std::string url(OSUtils::jsonString(vault["vaultURL"], "").c_str());
+ if (!url.empty()) {
+ _vaultURL = url;
+ }
+
+ const std::string token(OSUtils::jsonString(vault["vaultToken"], "").c_str());
+ if (!token.empty()) {
+ _vaultToken = token;
+ }
+
+ const std::string path(OSUtils::jsonString(vault["vaultPath"], "").c_str());
+ if (!path.empty()) {
+ _vaultPath = path;
+ }
+ }
+
+ // 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;
+ }
+#endif
}
// Checks if a managed IP or route target is allowed
@@ -2082,8 +2159,89 @@ public:
}
}
+#if ZT_VAULT_SUPPORT
+ inline bool nodeVaultPutIdentity(enum ZT_StateObjectType type, const void *data, int len)
+ {
+ 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 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;
bool secure = false;
@@ -2150,8 +2308,96 @@ public:
}
}
+#if ZT_VAULT_SUPPORT
+ inline int nodeVaultGetIdentity(enum ZT_StateObjectType type, void *data, unsigned int maxlen)
+ {
+ 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 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) {
case ZT_STATE_OBJECT_IDENTITY_PUBLIC:
@@ -2179,6 +2425,17 @@ public:
if (f) {
int n = (int)fread(data,1,maxlen,f);
fclose(f);
+#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.
+ if (nodeVaultPutIdentity(type, data, n)) {
+ unlink(p);
+ }
+ }
+#endif
if (n >= 0)
return n;
}