Browse Source

Add HashiCorp Vault storage of ZeroTier's public & secret identity

Adds a "vault" section to local.conf.  Example local.conf:

{
  "config": {
    "vault": {
      "vaultURL": "https://some.vault.host:8200",
      "vaultToken": "my-super-secret-vault-token",
      "vaultPath": "secure/place/to/put/identity"
    }
}

Additionally, the following environment variables can be set.  Environment variables override local.conf:

VAULT_ADDR
VAULT_TOKEN
VAULT_PATH

Identities will be placed in the keys "public" and "secret" under the user specified path.  If no path is specified, they will be placed in the token specific cubbyhole.

If identity.public and identity.secret exist on disk and vault is configured, they will be automatically added to Vault and removed from disk.

TODO:
 *  Decide behavior for if Vault cannot be reached.
 *  Add libcurl as a dependency in Linux & Mac builds
 *  Add libcurl as a requirement for linux packages
Grant Limberg 7 years ago
parent
commit
7793060723
1 changed files with 224 additions and 38 deletions
  1. 224 38
      service/OneService.cpp

+ 224 - 38
service/OneService.cpp

@@ -81,6 +81,12 @@
 #include "../ext/http-parser/http_parser.h"
 #include "../ext/http-parser/http_parser.h"
 #endif
 #endif
 
 
+#if ZT_VAULT_SUPPORT
+extern "C" {
+#include <curl/curl.h>
+}
+#endif
+
 #include "../ext/json/json.hpp"
 #include "../ext/json/json.hpp"
 
 
 using json = nlohmann::json;
 using json = nlohmann::json;
@@ -158,6 +164,14 @@ namespace ZeroTier { typedef BSDEthernetTap EthernetTap; }
 // TCP activity timeout
 // TCP activity timeout
 #define ZT_TCP_ACTIVITY_TIMEOUT 60000
 #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 ZeroTier {
 
 
 namespace {
 namespace {
@@ -478,10 +492,12 @@ public:
 #endif
 #endif
 
 
 	// HashiCorp Vault Settings
 	// HashiCorp Vault Settings
+#if ZT_VAULT_SUPPORT
 	bool _vaultEnabled;
 	bool _vaultEnabled;
 	std::string _vaultURL;
 	std::string _vaultURL;
 	std::string _vaultToken;
 	std::string _vaultToken;
 	std::string _vaultPath; // defaults to cubbyhole/zerotier/identity.secret for per-access key storage
 	std::string _vaultPath; // defaults to cubbyhole/zerotier/identity.secret for per-access key storage
+#endif
 
 
 	// Set to false to force service to stop
 	// Set to false to force service to stop
 	volatile bool _run;
 	volatile bool _run;
@@ -518,12 +534,16 @@ public:
 		,_vaultEnabled(false)
 		,_vaultEnabled(false)
 		,_vaultURL()
 		,_vaultURL()
 		,_vaultToken()
 		,_vaultToken()
-		,_vaultPath("cubbyhole/zerotier/identity.secret")
+		,_vaultPath("cubbyhole/zerotier")
 		,_run(true)
 		,_run(true)
 	{
 	{
 		_ports[0] = 0;
 		_ports[0] = 0;
 		_ports[1] = 0;
 		_ports[1] = 0;
 		_ports[2] = 0;
 		_ports[2] = 0;
+
+#if ZT_VAULT_SUPPORT
+		curl_global_init(CURL_GLOBAL_DEFAULT);
+#endif
 	}
 	}
 
 
 	virtual ~OneServiceImpl()
 	virtual ~OneServiceImpl()
@@ -560,20 +580,6 @@ public:
 				_authToken = _trimString(_authToken);
 				_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
 			// Read local configuration
 			std::vector<InetAddress> explicitBind;
 			std::vector<InetAddress> explicitBind;
 			{
 			{
@@ -663,14 +669,25 @@ public:
 					for(std::map<InetAddress,ZT_PhysicalPathConfiguration>::iterator i(ppc.begin());i!=ppc.end();++i)
 					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));
 						_node->setPhysicalPathConfiguration(reinterpret_cast<const struct sockaddr_storage *>(&(i->first)),&(i->second));
 				}
 				}
-
-				json &vaultConfig = _localConfig["vault"];
-				
 			}
 			}
 
 
 			// Apply other runtime configuration from local.conf
 			// Apply other runtime configuration from local.conf
 			applyLocalConfig();
 			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
 			// 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
 			const int portTrials = (_primaryPort == 0) ? 256 : 1; // if port is 0, pick random
 			for(int k=0;k<portTrials;++k) {
 			for(int k=0;k<portTrials;++k) {
@@ -1524,22 +1541,43 @@ public:
 			}
 			}
 		}
 		}
 
 
-		json &vault = settings["valut"];
+		json &vault = settings["vault"];
 		if (vault.is_object()) {
 		if (vault.is_object()) {
 			const std::string url(OSUtils::jsonString(vault["vaultURL"], "").c_str());
 			const std::string url(OSUtils::jsonString(vault["vaultURL"], "").c_str());
-			if (!url.empty())
+			if (!url.empty()) {
 				_vaultURL = url;
 				_vaultURL = url;
+			}
 
 
 			const std::string token(OSUtils::jsonString(vault["vaultToken"], "").c_str());
 			const std::string token(OSUtils::jsonString(vault["vaultToken"], "").c_str());
-			if (!token.empty())
+			if (!token.empty()) {
 				_vaultToken = token;
 				_vaultToken = token;
+			}
 
 
 			const std::string path(OSUtils::jsonString(vault["vaultPath"], "").c_str());
 			const std::string path(OSUtils::jsonString(vault["vaultPath"], "").c_str());
-			if (!path.empty())
+			if (!path.empty()) {
 				_vaultPath = path;
 				_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)
 	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];
 		char p[1024];
 		FILE *f;
 		FILE *f;
@@ -2186,21 +2295,96 @@ public:
 			OSUtils::rm(p);
 			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)
 	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)
 			if (retval >= 0)
 				return retval;
 				return retval;
 
 
 			// else continue file based lookup
 			// else continue file based lookup
 		}
 		}
+#endif
 
 
 		char p[4096];
 		char p[4096];
 		switch(type) {
 		switch(type) {
@@ -2229,15 +2413,17 @@ public:
 		if (f) {
 		if (f) {
 			int n = (int)fread(data,1,maxlen,f);
 			int n = (int)fread(data,1,maxlen,f);
 			fclose(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
 				// If we've gotten here while Vault is enabled, Vault does not know the key and it's been
 				// read from disk instead.
 				// read from disk instead.
 				//
 				//
 				// We should put the value in Vault and remove the local file.
 				// 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)
 			if (n >= 0)
 				return n;
 				return n;
 		}
 		}