mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-20 19:56:00 +00:00
@@ -66,7 +66,12 @@
|
||||
// Max number of requests to queue up before rejecting further requests.
|
||||
// Defaults to 0, which disables the limit.
|
||||
"max_queue_size": 500,
|
||||
"admin_password": "xrp"
|
||||
// If request contains header with authorization, Clio will check if it matches this value's hash
|
||||
// If matches, the request will be considered as admin request
|
||||
"admin_password": "xrp",
|
||||
// If local_admin is true, Clio will consider requests come from 127.0.0.1 as admin requests
|
||||
// It's true by default unless admin_password is set,'local_admin' : true and 'admin_password' can not be set at the same time
|
||||
"local_amdin": false
|
||||
},
|
||||
// Overrides log level on a per logging channel.
|
||||
// Defaults to global "log_level" for each unspecified channel.
|
||||
|
||||
@@ -298,6 +298,20 @@ make_HttpServer(
|
||||
auto const address = boost::asio::ip::make_address(serverConfig.value<std::string>("ip"));
|
||||
auto const port = serverConfig.value<unsigned short>("port");
|
||||
auto adminPassword = serverConfig.maybeValue<std::string>("admin_password");
|
||||
auto const localAdmin = serverConfig.maybeValue<bool>("local_admin");
|
||||
|
||||
// Throw config error when localAdmin is true and admin_password is also set
|
||||
if (localAdmin && localAdmin.value() && adminPassword) {
|
||||
LOG(log.error()) << "local_admin is true but admin_password is also set, please specify only one method "
|
||||
"to authorize admin";
|
||||
throw std::logic_error("Admin config error, local_admin and admin_password can not be set together.");
|
||||
}
|
||||
// Throw config error when localAdmin is false but admin_password is not set
|
||||
if (localAdmin && !localAdmin.value() && !adminPassword) {
|
||||
LOG(log.error()) << "local_admin is false but admin_password is not set, please specify one method "
|
||||
"to authorize admin";
|
||||
throw std::logic_error("Admin config error, one method must be specified to authorize admin.");
|
||||
}
|
||||
|
||||
auto server = std::make_shared<HttpServer<HandlerType>>(
|
||||
ioc,
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <web/impl/AdminVerificationStrategy.h>
|
||||
|
||||
#include <ripple/protocol/digest.h>
|
||||
|
||||
namespace web::detail {
|
||||
|
||||
bool
|
||||
@@ -27,9 +29,16 @@ IPAdminVerificationStrategy::isAdmin(RequestType const&, std::string_view ip) co
|
||||
return ip == "127.0.0.1";
|
||||
}
|
||||
|
||||
PasswordAdminVerificationStrategy::PasswordAdminVerificationStrategy(std::string password)
|
||||
: password_(std::move(password))
|
||||
PasswordAdminVerificationStrategy::PasswordAdminVerificationStrategy(std::string const& password)
|
||||
{
|
||||
ripple::sha256_hasher hasher;
|
||||
hasher(password.data(), password.size());
|
||||
auto const d = static_cast<ripple::sha256_hasher::result_type>(hasher);
|
||||
ripple::uint256 sha256;
|
||||
std::memcpy(sha256.data(), d.data(), d.size());
|
||||
passwordSha256_ = ripple::to_string(sha256);
|
||||
// make sure it's uppercase
|
||||
std::transform(passwordSha256_.begin(), passwordSha256_.end(), passwordSha256_.begin(), ::toupper);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -40,8 +49,9 @@ PasswordAdminVerificationStrategy::isAdmin(RequestType const& request, std::stri
|
||||
// No Authorization header
|
||||
return false;
|
||||
}
|
||||
|
||||
return it->value() == password_;
|
||||
std::string userAuth(it->value());
|
||||
std::transform(userAuth.begin(), userAuth.end(), userAuth.begin(), ::toupper);
|
||||
return passwordSha256_ == userAuth;
|
||||
}
|
||||
|
||||
std::unique_ptr<AdminVerificationStrategy>
|
||||
|
||||
@@ -57,10 +57,10 @@ public:
|
||||
|
||||
class PasswordAdminVerificationStrategy : public AdminVerificationStrategy {
|
||||
private:
|
||||
std::string password_;
|
||||
std::string passwordSha256_;
|
||||
|
||||
public:
|
||||
PasswordAdminVerificationStrategy(std::string password);
|
||||
PasswordAdminVerificationStrategy(std::string const& password);
|
||||
|
||||
/**
|
||||
* @brief Checks whether request is from a host that is considered authorized as admin using
|
||||
|
||||
@@ -44,6 +44,8 @@ TEST_F(IPAdminVerificationStrategyTest, IsAdminOnlyForIP_127_0_0_1)
|
||||
class PasswordAdminVerificationStrategyTest : public NoLoggerFixture {
|
||||
protected:
|
||||
const std::string password_ = "secret";
|
||||
const std::string passwordHash_ = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b";
|
||||
|
||||
web::detail::PasswordAdminVerificationStrategy strat_{password_};
|
||||
|
||||
static http::request<http::string_body>
|
||||
@@ -57,8 +59,8 @@ protected:
|
||||
|
||||
TEST_F(PasswordAdminVerificationStrategyTest, IsAdminReturnsTrueOnlyForValidPasswordInAuthHeader)
|
||||
{
|
||||
EXPECT_TRUE(strat_.isAdmin(makeRequest(password_), ""));
|
||||
EXPECT_TRUE(strat_.isAdmin(makeRequest(password_), "123"));
|
||||
EXPECT_TRUE(strat_.isAdmin(makeRequest(passwordHash_), ""));
|
||||
EXPECT_TRUE(strat_.isAdmin(makeRequest(passwordHash_), "123"));
|
||||
|
||||
// Wrong password
|
||||
EXPECT_FALSE(strat_.isAdmin(makeRequest("SECRET"), ""));
|
||||
@@ -70,7 +72,7 @@ TEST_F(PasswordAdminVerificationStrategyTest, IsAdminReturnsTrueOnlyForValidPass
|
||||
EXPECT_FALSE(strat_.isAdmin(makeRequest("a"), "127.0.0.1"));
|
||||
|
||||
// Wrong header
|
||||
EXPECT_FALSE(strat_.isAdmin(makeRequest(password_, http::field::authentication_info), ""));
|
||||
EXPECT_FALSE(strat_.isAdmin(makeRequest(passwordHash_, http::field::authentication_info), ""));
|
||||
}
|
||||
|
||||
struct MakeAdminVerificationStrategyTestParams {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <util/TestHttpSyncClient.h>
|
||||
#include <web/Server.h>
|
||||
|
||||
#include <ripple/protocol/digest.h>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <gtest/gtest.h>
|
||||
@@ -398,6 +399,49 @@ static auto constexpr JSONServerConfigWithAdminPassword = R"JSON(
|
||||
}
|
||||
)JSON";
|
||||
|
||||
static auto constexpr JSONServerConfigWithAdminPasswordWithFalseLocalAdmin = R"JSON(
|
||||
{
|
||||
"server":{
|
||||
"ip": "0.0.0.0",
|
||||
"port": 8888,
|
||||
"admin_password": "secret"
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
static auto constexpr JSONServerConfigWithLocalAdmin = R"JSON(
|
||||
{
|
||||
"server":{
|
||||
"ip": "0.0.0.0",
|
||||
"port": 8888,
|
||||
"local_admin": true
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
static auto constexpr JSONServerConfigWithBothAdminPasswordAndLocalAdminFalse = R"JSON(
|
||||
{
|
||||
"server":{
|
||||
"ip": "0.0.0.0",
|
||||
"port": 8888,
|
||||
"admin_password": "secret",
|
||||
"local_admin": false
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
static auto constexpr JSONServerConfigWithNoSpecifiedAdmin = R"JSON(
|
||||
{
|
||||
"server":{
|
||||
"ip": "0.0.0.0",
|
||||
"port": 8888
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
// get this value from online sha256 generator
|
||||
static auto constexpr SecertSha256 = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b";
|
||||
|
||||
class AdminCheckExecutor {
|
||||
public:
|
||||
void
|
||||
@@ -414,6 +458,7 @@ public:
|
||||
};
|
||||
|
||||
struct WebServerAdminTestParams {
|
||||
std::string config;
|
||||
std::vector<WebHeader> headers;
|
||||
std::string expectedResponse;
|
||||
};
|
||||
@@ -423,7 +468,7 @@ class WebServerAdminTest : public WebServerTest, public ::testing::WithParamInte
|
||||
TEST_P(WebServerAdminTest, WsAdminCheck)
|
||||
{
|
||||
auto e = std::make_shared<AdminCheckExecutor>();
|
||||
Config const serverConfig{boost::json::parse(JSONServerConfigWithAdminPassword)};
|
||||
Config const serverConfig{boost::json::parse(GetParam().config)};
|
||||
auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuardOverload, e);
|
||||
WebSocketSyncClient wsClient;
|
||||
wsClient.connect("localhost", "8888", GetParam().headers);
|
||||
@@ -436,7 +481,7 @@ TEST_P(WebServerAdminTest, WsAdminCheck)
|
||||
TEST_P(WebServerAdminTest, HttpAdminCheck)
|
||||
{
|
||||
auto e = std::make_shared<AdminCheckExecutor>();
|
||||
Config const serverConfig{boost::json::parse(JSONServerConfigWithAdminPassword)};
|
||||
Config const serverConfig{boost::json::parse(GetParam().config)};
|
||||
auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuardOverload, e);
|
||||
std::string const request = "Why hello";
|
||||
auto const res = HttpSyncClient::syncPost("localhost", "8888", request, GetParam().headers);
|
||||
@@ -447,14 +492,74 @@ INSTANTIATE_TEST_CASE_P(
|
||||
WebServerAdminTestsSuit,
|
||||
WebServerAdminTest,
|
||||
::testing::Values(
|
||||
WebServerAdminTestParams{.headers = {}, .expectedResponse = "user"},
|
||||
WebServerAdminTestParams{.headers = {WebHeader(http::field::authorization, "")}, .expectedResponse = "user"},
|
||||
WebServerAdminTestParams{.headers = {WebHeader(http::field::authorization, "s")}, .expectedResponse = "user"},
|
||||
WebServerAdminTestParams{
|
||||
.headers = {WebHeader(http::field::authorization, "secret")},
|
||||
.config = JSONServerConfigWithAdminPassword,
|
||||
.headers = {},
|
||||
.expectedResponse = "user"},
|
||||
WebServerAdminTestParams{
|
||||
.config = JSONServerConfigWithAdminPassword,
|
||||
.headers = {WebHeader(http::field::authorization, "")},
|
||||
.expectedResponse = "user"},
|
||||
WebServerAdminTestParams{
|
||||
.config = JSONServerConfigWithAdminPassword,
|
||||
.headers = {WebHeader(http::field::authorization, "s")},
|
||||
.expectedResponse = "user"},
|
||||
WebServerAdminTestParams{
|
||||
.config = JSONServerConfigWithAdminPassword,
|
||||
.headers = {WebHeader(http::field::authorization, SecertSha256)},
|
||||
.expectedResponse = "admin"},
|
||||
WebServerAdminTestParams{
|
||||
.headers = {WebHeader(http::field::authentication_info, "secret")},
|
||||
.expectedResponse = "user"}
|
||||
.config = JSONServerConfigWithAdminPasswordWithFalseLocalAdmin,
|
||||
.headers = {WebHeader(http::field::authorization, SecertSha256)},
|
||||
.expectedResponse = "admin"},
|
||||
WebServerAdminTestParams{
|
||||
.config = JSONServerConfigWithBothAdminPasswordAndLocalAdminFalse,
|
||||
.headers = {WebHeader(http::field::authorization, SecertSha256)},
|
||||
.expectedResponse = "admin"},
|
||||
WebServerAdminTestParams{
|
||||
.config = JSONServerConfigWithAdminPassword,
|
||||
.headers = {WebHeader(http::field::authentication_info, SecertSha256)},
|
||||
.expectedResponse = "user"},
|
||||
WebServerAdminTestParams{.config = JSONServerConfigWithLocalAdmin, .headers = {}, .expectedResponse = "admin"},
|
||||
WebServerAdminTestParams{
|
||||
.config = JSONServerConfigWithNoSpecifiedAdmin,
|
||||
.headers = {},
|
||||
.expectedResponse = "admin"}
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
TEST_F(WebServerTest, AdminErrorCfgTestBothAdminPasswordAndLocalAdminSet)
|
||||
{
|
||||
static auto constexpr JSONServerConfigWithBothAdminPasswordAndLocalAdmin = R"JSON(
|
||||
{
|
||||
"server":{
|
||||
"ip": "0.0.0.0",
|
||||
"port": 8888,
|
||||
"admin_password": "secret",
|
||||
"local_admin": true
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
auto e = std::make_shared<AdminCheckExecutor>();
|
||||
Config const serverConfig{boost::json::parse(JSONServerConfigWithBothAdminPasswordAndLocalAdmin)};
|
||||
EXPECT_THROW(web::make_HttpServer(serverConfig, ctx, std::nullopt, dosGuardOverload, e), std::logic_error);
|
||||
}
|
||||
|
||||
TEST_F(WebServerTest, AdminErrorCfgTestBothAdminPasswordAndLocalAdminFalse)
|
||||
{
|
||||
static auto constexpr JSONServerConfigWithNoAdminPasswordAndLocalAdminFalse = R"JSON(
|
||||
{
|
||||
"server":{
|
||||
"ip": "0.0.0.0",
|
||||
"port": 8888,
|
||||
"local_admin": false
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
auto e = std::make_shared<AdminCheckExecutor>();
|
||||
Config const serverConfig{boost::json::parse(JSONServerConfigWithNoAdminPasswordAndLocalAdminFalse)};
|
||||
EXPECT_THROW(web::make_HttpServer(serverConfig, ctx, std::nullopt, dosGuardOverload, e), std::logic_error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user