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
This commit is contained in:
Grant Limberg 2018-02-16 14:30:27 -08:00
parent 84302ae9c7
commit 7793060723

View file

@ -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;
}