aboutsummaryrefslogtreecommitdiff
path: root/src/zenhttp/security/passwordsecurity.cpp
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2026-02-17 14:00:53 +0100
committerGitHub Enterprise <[email protected]>2026-02-17 14:00:53 +0100
commit5e1e23e209eec75a396c18f8eee3d93a9e196bfc (patch)
tree31b2b3938468aacdb0621e8b932cb9e9738ee918 /src/zenhttp/security/passwordsecurity.cpp
parentmisc fixes brought over from sb/proto (#759) (diff)
downloadzen-5e1e23e209eec75a396c18f8eee3d93a9e196bfc.tar.xz
zen-5e1e23e209eec75a396c18f8eee3d93a9e196bfc.zip
add http server root password protection (#757)
- Feature: Added `--security-config-path` option to zenserver to configure security settings - Expects a path to a .json file - Default is an empty path resulting in no extra security settings and legacy behavior - Current support is a top level filter of incoming http requests restricted to the `password` type - `password` type will check the `Authorization` header and match it to the selected authorization strategy - Currently the security settings is very basic and configured to a fixed username+password at startup { "http" { "root": { "filter": { "type": "password", "config": { "password": { "username": "<username>", "password": "<password>" }, "protect-machine-local-requests": false, "unprotected-uris": [ "/health/", "/health/info", "/health/version" ] } } } } }
Diffstat (limited to 'src/zenhttp/security/passwordsecurity.cpp')
-rw-r--r--src/zenhttp/security/passwordsecurity.cpp164
1 files changed, 57 insertions, 107 deletions
diff --git a/src/zenhttp/security/passwordsecurity.cpp b/src/zenhttp/security/passwordsecurity.cpp
index 37be9a018..a8fb9c3f5 100644
--- a/src/zenhttp/security/passwordsecurity.cpp
+++ b/src/zenhttp/security/passwordsecurity.cpp
@@ -13,13 +13,13 @@
namespace zen {
using namespace std::literals;
-PasswordSecurity::PasswordSecurity(const PasswordSecurityConfiguration& Config) : m_Config(Config)
+PasswordSecurity::PasswordSecurity(const Configuration& Config) : m_Config(Config)
{
- m_UnprotectedUrlHashes.reserve(m_Config.UnprotectedUris.size());
+ m_UnprotectedUriHashes.reserve(m_Config.UnprotectedUris.size());
for (uint32_t Index = 0; Index < m_Config.UnprotectedUris.size(); Index++)
{
const std::string& UnprotectedUri = m_Config.UnprotectedUris[Index];
- if (auto Result = m_UnprotectedUrlHashes.insert({HashStringDjb2(UnprotectedUri), Index}); !Result.second)
+ if (auto Result = m_UnprotectedUriHashes.insert({HashStringDjb2(UnprotectedUri), Index}); !Result.second)
{
throw std::runtime_error(fmt::format(
"password security unprotected uris does not generate unique hashes. Uri #{} ('{}') collides with uri #{} ('{}')",
@@ -32,35 +32,30 @@ PasswordSecurity::PasswordSecurity(const PasswordSecurityConfiguration& Config)
}
bool
-PasswordSecurity::IsUnprotectedUri(std::string_view Uri) const
+PasswordSecurity::IsUnprotectedUri(std::string_view BaseUri, std::string_view RelativeUri) const
{
if (!m_Config.UnprotectedUris.empty())
{
- uint32_t UriHash = HashStringDjb2(Uri);
- if (auto It = m_UnprotectedUrlHashes.find(UriHash); It != m_UnprotectedUrlHashes.end())
+ uint32_t UriHash = HashStringDjb2(std::array<const std::string_view, 2>{BaseUri, RelativeUri});
+ if (auto It = m_UnprotectedUriHashes.find(UriHash); It != m_UnprotectedUriHashes.end())
{
- if (m_Config.UnprotectedUris[It->second] == Uri)
+ const std::string_view& UnprotectedUri = m_Config.UnprotectedUris[It->second];
+ if (UnprotectedUri.length() == BaseUri.length() + RelativeUri.length())
{
- return true;
+ if (UnprotectedUri.substr(0, BaseUri.length()) == BaseUri && UnprotectedUri.substr(BaseUri.length()) == RelativeUri)
+ {
+ return true;
+ }
}
}
}
return false;
}
-PasswordSecurityConfiguration
-ReadPasswordSecurityConfiguration(CbObjectView ConfigObject)
-{
- return PasswordSecurityConfiguration{
- .Password = std::string(ConfigObject["password"sv].AsString()),
- .ProtectMachineLocalRequests = ConfigObject["protect-machine-local-requests"sv].AsBool(),
- .UnprotectedUris = compactbinary_helpers::ReadArray<std::string>("unprotected-urls"sv, ConfigObject)};
-}
-
bool
-PasswordSecurity::IsAllowed(std::string_view InPassword, std::string_view Uri, bool IsMachineLocalRequest)
+PasswordSecurity::IsAllowed(std::string_view InPassword, std::string_view BaseUri, std::string_view RelativeUri, bool IsMachineLocalRequest)
{
- if (IsUnprotectedUri(Uri))
+ if (IsUnprotectedUri(BaseUri, RelativeUri))
{
return true;
}
@@ -81,119 +76,74 @@ PasswordSecurity::IsAllowed(std::string_view InPassword, std::string_view Uri, b
#if ZEN_WITH_TESTS
-TEST_CASE("passwordsecurity.readconfig")
-{
- auto ReadConfigJson = [](std::string_view Json) {
- std::string JsonError;
- CbObject Config = LoadCompactBinaryFromJson(Json, JsonError).AsObject();
- REQUIRE(JsonError.empty());
- return Config;
- };
-
- {
- PasswordSecurityConfiguration EmptyConfig = ReadPasswordSecurityConfiguration(CbObject());
- CHECK(EmptyConfig.Password.empty());
- CHECK(!EmptyConfig.ProtectMachineLocalRequests);
- CHECK(EmptyConfig.UnprotectedUris.empty());
- }
-
- {
- const std::string_view SimpleConfigJson =
- "{\n"
- " \"password\": \"1234\"\n"
- "}";
- PasswordSecurityConfiguration SimpleConfig = ReadPasswordSecurityConfiguration(ReadConfigJson(SimpleConfigJson));
- CHECK(SimpleConfig.Password == "1234");
- CHECK(!SimpleConfig.ProtectMachineLocalRequests);
- CHECK(SimpleConfig.UnprotectedUris.empty());
- }
-
- {
- const std::string_view ComplexConfigJson =
- "{\n"
- " \"password\": \"1234\",\n"
- " \"protect-machine-local-requests\": true,\n"
- " \"unprotected-urls\": [\n"
- " \"/health\",\n"
- " \"/health/info\",\n"
- " \"/health/version\"\n"
- " ]\n"
- "}";
- PasswordSecurityConfiguration ComplexConfig = ReadPasswordSecurityConfiguration(ReadConfigJson(ComplexConfigJson));
- CHECK(ComplexConfig.Password == "1234");
- CHECK(ComplexConfig.ProtectMachineLocalRequests);
- CHECK(ComplexConfig.UnprotectedUris == std::vector<std::string>({"/health", "/health/info", "/health/version"}));
- }
-}
-
TEST_CASE("passwordsecurity.allowanything")
{
PasswordSecurity Anything({});
- CHECK(Anything.IsAllowed(""sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
- CHECK(Anything.IsAllowed(""sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(Anything.IsAllowed("thewrongpassword"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
- CHECK(Anything.IsAllowed("thewrongpassword"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(Anything.IsAllowed(""sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(Anything.IsAllowed(""sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(Anything.IsAllowed("thewrongpassword"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(Anything.IsAllowed("thewrongpassword"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
}
TEST_CASE("passwordsecurity.allowalllocal")
{
PasswordSecurity AllLocal({.Password = "123456"});
- CHECK(AllLocal.IsAllowed(""sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(!AllLocal.IsAllowed(""sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed(""sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(!AllLocal.IsAllowed(""sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
}
TEST_CASE("passwordsecurity.allowonlypassword")
{
PasswordSecurity AllLocal({.Password = "123456", .ProtectMachineLocalRequests = true});
- CHECK(!AllLocal.IsAllowed(""sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(!AllLocal.IsAllowed(""sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
- CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(!AllLocal.IsAllowed(""sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(!AllLocal.IsAllowed(""sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
}
TEST_CASE("passwordsecurity.allowsomeexternaluris")
{
PasswordSecurity AllLocal(
{.Password = "123456", .ProtectMachineLocalRequests = false, .UnprotectedUris = std::vector<std::string>({"/free/access", "/ok"})});
- CHECK(AllLocal.IsAllowed(""sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(!AllLocal.IsAllowed(""sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed(""sv, "/free/access", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed(""sv, "/ok", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/free/access", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/ok", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed(""sv, "/free/access", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed(""sv, "/ok", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/free/access", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/ok", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed(""sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(!AllLocal.IsAllowed(""sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed(""sv, "/free", "/access", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed(""sv, "/ok", "", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/free", "/access", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/ok", "", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed(""sv, "/free", "/access", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed(""sv, "/ok", "", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/free", "/access", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/ok", "", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
}
TEST_CASE("passwordsecurity.allowsomelocaluris")
{
PasswordSecurity AllLocal(
{.Password = "123456", .ProtectMachineLocalRequests = true, .UnprotectedUris = std::vector<std::string>({"/free/access", "/ok"})});
- CHECK(!AllLocal.IsAllowed(""sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(!AllLocal.IsAllowed(""sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
- CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ true));
- CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed(""sv, "/free/access", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed(""sv, "/ok", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/free/access", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/ok", /*IsMachineLocalRequest*/ true));
- CHECK(AllLocal.IsAllowed(""sv, "/free/access", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed(""sv, "/ok", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/free/access", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/ok", /*IsMachineLocalRequest*/ false));
- CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(!AllLocal.IsAllowed(""sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(!AllLocal.IsAllowed(""sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ true));
+ CHECK(!AllLocal.IsAllowed("thewrongpassword"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed(""sv, "/free", "/access", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed(""sv, "/ok", "", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/free", "/access", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/ok", "", /*IsMachineLocalRequest*/ true));
+ CHECK(AllLocal.IsAllowed(""sv, "/free", "/access", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed(""sv, "/ok", "", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/free", "/access", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed("thewrongpassword"sv, "/ok", "", /*IsMachineLocalRequest*/ false));
+ CHECK(AllLocal.IsAllowed("123456"sv, "/supersecret", "/uri", /*IsMachineLocalRequest*/ false));
}
TEST_CASE("passwordsecurity.conflictingunprotecteduris")