mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-04-16 12:06:55 +02:00
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:
parent
84302ae9c7
commit
7793060723
1 changed files with 224 additions and 38 deletions
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue