aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorZousar Shaker <[email protected]>2026-03-20 05:45:44 -0600
committerGitHub Enterprise <[email protected]>2026-03-20 12:45:44 +0100
commitd44a692aaeb6f78e8cf7520236b21977333fa3c3 (patch)
treeb8ae4ae8b2e6b51ae6fee2123424e2abd7cebb74 /src
parentZen disk benchmark utility (#868) (diff)
downloadzen-d44a692aaeb6f78e8cf7520236b21977333fa3c3.tar.xz
zen-d44a692aaeb6f78e8cf7520236b21977333fa3c3.zip
Zs/consul token (#870)
- Feature: Added support for consul token passed via environment variable, and specified a default env var name of CONSUL_HTTP_TOKEN for it in hub mode
Diffstat (limited to 'src')
-rw-r--r--src/zenserver-test/hub-tests.cpp56
-rw-r--r--src/zenserver/hub/zenhubserver.cpp32
-rw-r--r--src/zenserver/hub/zenhubserver.h1
-rw-r--r--src/zenutil/consul/consul.cpp66
-rw-r--r--src/zenutil/include/zenutil/consul.h6
5 files changed, 146 insertions, 15 deletions
diff --git a/src/zenserver-test/hub-tests.cpp b/src/zenserver-test/hub-tests.cpp
index 958a0b050..29c3b76ba 100644
--- a/src/zenserver-test/hub-tests.cpp
+++ b/src/zenserver-test/hub-tests.cpp
@@ -21,6 +21,11 @@
# include <zenutil/consul.h>
# include <zencore/thread.h>
# include <zencore/timer.h>
+# if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+# else
+# include <cstdlib>
+# endif
namespace zen::tests::hub {
@@ -290,6 +295,57 @@ TEST_CASE("hub.consul.hub.registration")
ConsulProc.StopConsulAgent();
}
+TEST_CASE("hub.consul.hub.registration.token")
+{
+ // Set an env var that the server will read its Consul token from.
+ // Children inherit parent environment, so the spawned server will see it.
+ constexpr const char* TokenEnvVarName = "ZEN_TEST_CONSUL_TOKEN";
+ constexpr const char* TokenValue = "test-token-value";
+# if ZEN_PLATFORM_WINDOWS
+ char PrevBuf[1024] = {};
+ DWORD PrevLen = GetEnvironmentVariableA(TokenEnvVarName, PrevBuf, sizeof(PrevBuf));
+ REQUIRE(PrevLen < sizeof(PrevBuf));
+ SetEnvironmentVariableA(TokenEnvVarName, TokenValue);
+ auto EnvCleanup = MakeGuard([PrevEnvValue = std::string(PrevBuf, PrevLen), HadPrevToken = PrevLen > 0] {
+ SetEnvironmentVariableA(TokenEnvVarName, HadPrevToken ? PrevEnvValue.c_str() : nullptr);
+ });
+# else
+ const char* PrevEnvPtr = getenv(TokenEnvVarName);
+ setenv(TokenEnvVarName, TokenValue, /*overwrite=*/1);
+ auto EnvCleanup = MakeGuard([PrevEnvValue = std::string(PrevEnvPtr ? PrevEnvPtr : ""), HadPrevToken = PrevEnvPtr != nullptr] {
+ if (HadPrevToken)
+ {
+ setenv(TokenEnvVarName, PrevEnvValue.c_str(), /*overwrite=*/1);
+ }
+ else
+ {
+ unsetenv(TokenEnvVarName);
+ }
+ });
+# endif
+
+ consul::ConsulProcess ConsulProc;
+ ConsulProc.SpawnConsulAgent();
+
+ ZenServerInstance Instance(TestEnv, ZenServerInstance::ServerMode::kHubServer);
+ const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(
+ "--consul-endpoint=http://localhost:8500/ --instance-id=test-instance "
+ "--consul-token-env=ZEN_TEST_CONSUL_TOKEN");
+ REQUIRE(PortNumber != 0);
+
+ // Use a plain client — dev-mode Consul doesn't enforce ACLs, but the
+ // server has exercised the ConsulTokenEnv -> GetEnvVariable -> ConsulClient path.
+ consul::ConsulClient Client("http://localhost:8500/");
+
+ REQUIRE(WaitForConsulService(Client, "zen-hub-test-instance", true, 5000));
+
+ Instance.Shutdown();
+
+ CHECK(!Client.HasService("zen-hub-test-instance"));
+
+ ConsulProc.StopConsulAgent();
+}
+
TEST_CASE("hub.consul.provision.registration")
{
consul::ConsulProcess ConsulProc;
diff --git a/src/zenserver/hub/zenhubserver.cpp b/src/zenserver/hub/zenhubserver.cpp
index 7bced2809..45a3211b2 100644
--- a/src/zenserver/hub/zenhubserver.cpp
+++ b/src/zenserver/hub/zenhubserver.cpp
@@ -23,6 +23,13 @@ ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
+const std::string&
+GetDefaultConsulTokenEnvVariableName()
+{
+ static const std::string Name = "CONSUL_HTTP_TOKEN";
+ return Name;
+}
+
void
ZenHubServerConfigurator::AddCliOptions(cxxopts::Options& Options)
{
@@ -58,6 +65,14 @@ ZenHubServerConfigurator::AddCliOptions(cxxopts::Options& Options)
Options.add_option("hub",
"",
+ "consul-token-env",
+ fmt::format("Name of environment variable that holds the consul access token (defaults to '{}')",
+ GetDefaultConsulTokenEnvVariableName()),
+ cxxopts::value<std::string>(m_ServerOptions.ConsulTokenEnv)->default_value(""),
+ "<envvariable>");
+
+ Options.add_option("hub",
+ "",
"hub-base-port-number",
"Base port number for provisioned instances",
cxxopts::value<uint16_t>(m_ServerOptions.HubBasePortNumber)->default_value("21000"),
@@ -342,9 +357,24 @@ ZenHubServer::InitializeConsulRegistration(const ZenHubServerConfig& ServerConfi
ZEN_INFO("Initializing Consul registration with endpoint: {}", ServerConfig.ConsulEndpoint);
+ std::string ConsulAccessTokenEnvName =
+ ServerConfig.ConsulTokenEnv.empty() ? GetDefaultConsulTokenEnvVariableName() : ServerConfig.ConsulTokenEnv;
+ std::string ConsulAccessToken = GetEnvVariable(ConsulAccessTokenEnvName);
+ if (ConsulAccessToken.empty())
+ {
+ if (!ServerConfig.ConsulTokenEnv.empty())
+ {
+ ZEN_WARN("Consul token environment variable '{}' is not set or empty", ServerConfig.ConsulTokenEnv);
+ }
+ }
+ else
+ {
+ ZEN_INFO("Consul token read from environment variable '{}'", ConsulAccessTokenEnvName);
+ }
+
try
{
- m_ConsulClient = std::make_unique<consul::ConsulClient>(ServerConfig.ConsulEndpoint);
+ m_ConsulClient = std::make_unique<consul::ConsulClient>(ServerConfig.ConsulEndpoint, ConsulAccessToken);
consul::ServiceRegistrationInfo Info;
Info.ServiceId = fmt::format("zen-hub-{}", ServerConfig.InstanceId);
diff --git a/src/zenserver/hub/zenhubserver.h b/src/zenserver/hub/zenhubserver.h
index 15efeaec9..33d813122 100644
--- a/src/zenserver/hub/zenhubserver.h
+++ b/src/zenserver/hub/zenhubserver.h
@@ -24,6 +24,7 @@ struct ZenHubServerConfig : public ZenServerConfig
std::string UpstreamNotificationEndpoint;
std::string InstanceId; // For use in notifications
std::string ConsulEndpoint; // If set, enables Consul service registration
+ std::string ConsulTokenEnv; // Environment variable name to read a Consul token from; defaults to CONSUL_HTTP_TOKEN if empty
uint16_t HubBasePortNumber = 21000;
int HubInstanceLimit = 1000;
bool HubUseJobObject = true;
diff --git a/src/zenutil/consul/consul.cpp b/src/zenutil/consul/consul.cpp
index 272096ebb..d463c0938 100644
--- a/src/zenutil/consul/consul.cpp
+++ b/src/zenutil/consul/consul.cpp
@@ -20,7 +20,7 @@ namespace zen::consul {
struct ConsulProcess::Impl
{
- Impl(std::string_view BaseUri) : m_HttpClient(BaseUri) {}
+ Impl(std::string_view BaseUri, std::string_view Token = "") : m_Token(Token), m_HttpClient(BaseUri) {}
~Impl() = default;
void SpawnConsulAgent()
@@ -44,10 +44,16 @@ struct ConsulProcess::Impl
// Poll to check when the agent is ready
+ HttpClient::KeyValueMap AdditionalHeaders;
+ if (!m_Token.empty())
+ {
+ AdditionalHeaders.Entries.emplace("X-Consul-Token", m_Token);
+ }
+
do
{
Sleep(100);
- HttpClient::Response Resp = m_HttpClient.Get("v1/status/leader");
+ HttpClient::Response Resp = m_HttpClient.Get("v1/status/leader", AdditionalHeaders);
if (Resp)
{
ZEN_INFO("Consul agent started successfully (waited {})", NiceTimeSpanMs(Timer.GetElapsedTimeMs()));
@@ -75,6 +81,7 @@ struct ConsulProcess::Impl
private:
ProcessHandle m_ProcessHandle;
+ std::string m_Token;
HttpClient m_HttpClient;
};
@@ -100,7 +107,7 @@ ConsulProcess::StopConsulAgent()
//////////////////////////////////////////////////////////////////////////
-ConsulClient::ConsulClient(std::string_view BaseUri) : m_HttpClient(BaseUri)
+ConsulClient::ConsulClient(std::string_view BaseUri, std::string_view Token) : m_Token(Token), m_HttpClient(BaseUri)
{
}
@@ -111,9 +118,13 @@ ConsulClient::~ConsulClient()
void
ConsulClient::SetKeyValue(std::string_view Key, std::string_view Value)
{
+ HttpClient::KeyValueMap AdditionalHeaders;
+ ApplyCommonHeaders(AdditionalHeaders);
+ AdditionalHeaders.Entries.emplace(HttpClient::Accept(HttpContentType::kJSON));
+
IoBuffer ValueBuffer = IoBufferBuilder::MakeFromMemory(MakeMemoryView(Value));
ValueBuffer.SetContentType(HttpContentType::kText);
- HttpClient::Response Result = m_HttpClient.Put(fmt::format("v1/kv/{}", Key), ValueBuffer, HttpClient::Accept(HttpContentType::kJSON));
+ HttpClient::Response Result = m_HttpClient.Put(fmt::format("v1/kv/{}", Key), ValueBuffer, AdditionalHeaders);
if (!Result)
{
throw runtime_error("ConsulClient::SetKeyValue() failed to set key '{}' ({})", Key, Result.ErrorMessage(""));
@@ -123,7 +134,10 @@ ConsulClient::SetKeyValue(std::string_view Key, std::string_view Value)
std::string
ConsulClient::GetKeyValue(std::string_view Key)
{
- HttpClient::Response Result = m_HttpClient.Get(fmt::format("v1/kv/{}?raw", Key));
+ HttpClient::KeyValueMap AdditionalHeaders;
+ ApplyCommonHeaders(AdditionalHeaders);
+
+ HttpClient::Response Result = m_HttpClient.Get(fmt::format("v1/kv/{}?raw", Key), AdditionalHeaders);
if (!Result)
{
throw runtime_error("ConsulClient::GetKeyValue() failed to get key '{}' ({})", Key, Result.ErrorMessage(""));
@@ -134,7 +148,10 @@ ConsulClient::GetKeyValue(std::string_view Key)
void
ConsulClient::DeleteKey(std::string_view Key)
{
- HttpClient::Response Result = m_HttpClient.Delete(fmt::format("v1/kv/{}", Key));
+ HttpClient::KeyValueMap AdditionalHeaders;
+ ApplyCommonHeaders(AdditionalHeaders);
+
+ HttpClient::Response Result = m_HttpClient.Delete(fmt::format("v1/kv/{}", Key), AdditionalHeaders);
if (!Result)
{
throw runtime_error("ConsulClient::DeleteKey() failed to delete key '{}' ({})", Key, Result.ErrorMessage(""));
@@ -146,6 +163,10 @@ ConsulClient::RegisterService(const ServiceRegistrationInfo& Info)
{
using namespace std::literals;
+ HttpClient::KeyValueMap AdditionalHeaders;
+ ApplyCommonHeaders(AdditionalHeaders);
+ AdditionalHeaders.Entries.emplace(HttpClient::Accept(HttpContentType::kJSON));
+
CbObjectWriter Writer;
{
Writer.AddString("ID"sv, Info.ServiceId);
@@ -178,7 +199,7 @@ ConsulClient::RegisterService(const ServiceRegistrationInfo& Info)
IoBuffer PayloadBuffer(IoBuffer::Wrap, SB.Data(), SB.Size());
PayloadBuffer.SetContentType(HttpContentType::kJSON);
- HttpClient::Response Result = m_HttpClient.Put("v1/agent/service/register", PayloadBuffer, HttpClient::Accept(HttpContentType::kJSON));
+ HttpClient::Response Result = m_HttpClient.Put("v1/agent/service/register", PayloadBuffer, AdditionalHeaders);
if (!Result)
{
@@ -192,8 +213,11 @@ ConsulClient::RegisterService(const ServiceRegistrationInfo& Info)
bool
ConsulClient::DeregisterService(std::string_view ServiceId)
{
- HttpClient::Response Result =
- m_HttpClient.Put(fmt::format("v1/agent/service/deregister/{}", ServiceId), HttpClient::Accept(HttpContentType::kJSON));
+ HttpClient::KeyValueMap AdditionalHeaders;
+ ApplyCommonHeaders(AdditionalHeaders);
+ AdditionalHeaders.Entries.emplace(HttpClient::Accept(HttpContentType::kJSON));
+
+ HttpClient::Response Result = m_HttpClient.Put(fmt::format("v1/agent/service/deregister/{}", ServiceId), AdditionalHeaders);
if (!Result)
{
@@ -204,6 +228,15 @@ ConsulClient::DeregisterService(std::string_view ServiceId)
return true;
}
+void
+ConsulClient::ApplyCommonHeaders(HttpClient::KeyValueMap& InOutHeaderMap)
+{
+ if (!m_Token.empty())
+ {
+ InOutHeaderMap.Entries.emplace("X-Consul-Token", m_Token);
+ }
+}
+
bool
ConsulClient::FindServiceInJson(std::string_view Json, std::string_view ServiceId)
{
@@ -235,7 +268,10 @@ ConsulClient::FindServiceInJson(std::string_view Json, std::string_view ServiceI
bool
ConsulClient::HasService(std::string_view ServiceId)
{
- HttpClient::Response Result = m_HttpClient.Get("v1/agent/services");
+ HttpClient::KeyValueMap AdditionalHeaders;
+ ApplyCommonHeaders(AdditionalHeaders);
+
+ HttpClient::Response Result = m_HttpClient.Get("v1/agent/services", AdditionalHeaders);
if (!Result)
{
return false;
@@ -246,10 +282,13 @@ ConsulClient::HasService(std::string_view ServiceId)
bool
ConsulClient::WatchService(std::string_view ServiceId, uint64_t& InOutIndex, int WaitSeconds)
{
+ HttpClient::KeyValueMap AdditionalHeaders;
+ ApplyCommonHeaders(AdditionalHeaders);
+
// Note: m_HttpClient uses unlimited HTTP timeout (Timeout{0}); the WaitSeconds parameter
// governs the server-side bound on the blocking query. Do not add a separate client timeout.
HttpClient::KeyValueMap Parameters({{"index", std::to_string(InOutIndex)}, {"wait", fmt::format("{}s", WaitSeconds)}});
- HttpClient::Response Result = m_HttpClient.Get("v1/agent/services", {}, Parameters);
+ HttpClient::Response Result = m_HttpClient.Get("v1/agent/services", AdditionalHeaders, Parameters);
if (!Result)
{
return false;
@@ -271,7 +310,10 @@ ConsulClient::WatchService(std::string_view ServiceId, uint64_t& InOutIndex, int
std::string
ConsulClient::GetAgentServicesJson()
{
- HttpClient::Response Result = m_HttpClient.Get("v1/agent/services");
+ HttpClient::KeyValueMap AdditionalHeaders;
+ ApplyCommonHeaders(AdditionalHeaders);
+
+ HttpClient::Response Result = m_HttpClient.Get("v1/agent/services", AdditionalHeaders);
if (!Result)
{
return "{}";
diff --git a/src/zenutil/include/zenutil/consul.h b/src/zenutil/include/zenutil/consul.h
index b5bfa42d1..7bf2ce437 100644
--- a/src/zenutil/include/zenutil/consul.h
+++ b/src/zenutil/include/zenutil/consul.h
@@ -28,7 +28,7 @@ struct ServiceRegistrationInfo
class ConsulClient
{
public:
- ConsulClient(std::string_view BaseUri);
+ ConsulClient(std::string_view BaseUri, std::string_view Token = "");
~ConsulClient();
ConsulClient(const ConsulClient&) = delete;
@@ -53,8 +53,10 @@ public:
private:
static bool FindServiceInJson(std::string_view Json, std::string_view ServiceId);
+ void ApplyCommonHeaders(HttpClient::KeyValueMap& InOutHeaderMap);
- HttpClient m_HttpClient;
+ std::string m_Token;
+ HttpClient m_HttpClient;
};
class ConsulProcess