mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-04 11:55:51 +00:00
Add hostname resolving to dosguard (#1000)
Fixes #983. Cassandra, ETL sorces and cache already support hostname resolving. Also added config to show missing includes by clangd.
This commit is contained in:
@@ -126,6 +126,7 @@ CheckOptions:
|
||||
readability-braces-around-statements.ShortStatementLines: 2
|
||||
bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true
|
||||
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc;::std::expected
|
||||
misc-include-cleaner.IgnoreHeaders: ".*/(detail|impl)/.*"
|
||||
|
||||
HeaderFilterRegex: '^.*/(src|unitests)/.*\.(h|hpp)$'
|
||||
WarningsAsErrors: '*'
|
||||
|
||||
5
.clangd
Normal file
5
.clangd
Normal file
@@ -0,0 +1,5 @@
|
||||
Diagnostics:
|
||||
UnusedIncludes: Strict
|
||||
MissingIncludes: Strict
|
||||
Includes:
|
||||
IgnoreHeader: ".*/(detail|impl)/.*"
|
||||
@@ -102,6 +102,7 @@ target_sources (clio PRIVATE
|
||||
## Web
|
||||
src/web/impl/AdminVerificationStrategy.cpp
|
||||
src/web/IntervalSweepHandler.cpp
|
||||
src/web/Resolver.cpp
|
||||
## RPC
|
||||
src/rpc/Errors.cpp
|
||||
src/rpc/Factories.cpp
|
||||
|
||||
69
src/web/Resolver.cpp
Normal file
69
src/web/Resolver.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include "web/Resolver.h"
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
|
||||
namespace web {
|
||||
|
||||
namespace {
|
||||
|
||||
// Check if the hostname is an IP address or a subnet.
|
||||
bool
|
||||
isAddress(std::string_view hostname)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
asio::ip::make_address(hostname.data(), ec);
|
||||
if (ec == boost::system::errc::success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
asio::ip::make_network_v4(hostname.data(), ec);
|
||||
if (ec == boost::system::errc::success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
asio::ip::make_network_v6(hostname.data(), ec);
|
||||
return ec == boost::system::errc::success;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<std::string>
|
||||
Resolver::resolve(std::string_view hostname, std::string_view service)
|
||||
{
|
||||
if (isAddress(hostname)) {
|
||||
return {std::string(hostname)};
|
||||
}
|
||||
|
||||
std::vector<std::string> endpoints;
|
||||
for (auto const& endpoint : resolver_.resolve(hostname, service)) {
|
||||
endpoints.push_back(endpoint.endpoint().address().to_string());
|
||||
}
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
} // namespace web
|
||||
60
src/web/Resolver.h
Normal file
60
src/web/Resolver.h
Normal file
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <concepts>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace web {
|
||||
|
||||
template <typename T>
|
||||
concept SomeResolver = requires(T t) {
|
||||
std::is_default_constructible_v<T>;
|
||||
{
|
||||
t.resolve(std::string_view{}, std::string_view{})
|
||||
} -> std::same_as<std::vector<std::string>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Simple hostnames to IP addresses resolver.
|
||||
*/
|
||||
class Resolver {
|
||||
public:
|
||||
/**
|
||||
* @brief Resolve hostname to IP addresses.
|
||||
*
|
||||
* @throw This method throws an exception when the hostname cannot be resolved.
|
||||
*
|
||||
* @param hostname Hostname to resolve
|
||||
* @param service Service to resolve (could be empty or port number or http)
|
||||
* @return std::vector<std::string> IP addresses of the hostname
|
||||
*/
|
||||
std::vector<std::string>
|
||||
resolve(std::string_view hostname, std::string_view service = "");
|
||||
|
||||
private:
|
||||
boost::asio::io_context ioContext_;
|
||||
boost::asio::ip::tcp::resolver resolver_{ioContext_};
|
||||
};
|
||||
|
||||
} // namespace web
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/config/Config.h"
|
||||
#include "web/Resolver.h"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
@@ -144,9 +145,10 @@ public:
|
||||
*
|
||||
* @param config The Clio config to use
|
||||
*/
|
||||
WhitelistHandler(util::Config const& config)
|
||||
template <SomeResolver HostnameResolverType = Resolver>
|
||||
WhitelistHandler(util::Config const& config, HostnameResolverType&& resolver = {})
|
||||
{
|
||||
std::unordered_set<std::string> const arr = getWhitelist(config);
|
||||
std::unordered_set<std::string> const arr = getWhitelist(config, std::forward<HostnameResolverType>(resolver));
|
||||
for (auto const& net : arr)
|
||||
whitelist_.add(net);
|
||||
}
|
||||
@@ -161,18 +163,28 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
template <SomeResolver HostnameResolverType>
|
||||
[[nodiscard]] static std::unordered_set<std::string>
|
||||
getWhitelist(util::Config const& config)
|
||||
getWhitelist(util::Config const& config, HostnameResolverType&& resolver)
|
||||
{
|
||||
using SetType = std::unordered_set<std::string> const;
|
||||
|
||||
auto whitelist = config.arrayOr("dos_guard.whitelist", {});
|
||||
auto const transform = [](auto const& elem) { return elem.template value<std::string>(); };
|
||||
|
||||
return SetType{
|
||||
std::unordered_set<std::string> const hostnames{
|
||||
boost::transform_iterator(std::begin(whitelist), transform),
|
||||
boost::transform_iterator(std::end(whitelist), transform)
|
||||
};
|
||||
|
||||
// resolve hostnames to ips
|
||||
std::unordered_set<std::string> ips;
|
||||
for (auto const& hostname : hostnames) {
|
||||
auto resolvedIps = resolver.resolve(hostname, "");
|
||||
for (auto& ip : resolvedIps) {
|
||||
ips.insert(std::move(ip));
|
||||
}
|
||||
};
|
||||
return ips;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace web
|
||||
|
||||
@@ -21,11 +21,26 @@
|
||||
#include "web/WhitelistHandler.h"
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
using namespace util;
|
||||
using namespace web;
|
||||
|
||||
struct WhitelistHandlerTest : NoLoggerFixture {};
|
||||
|
||||
TEST_F(WhitelistHandlerTest, TestWhiteListIPV4)
|
||||
{
|
||||
struct MockResolver {
|
||||
MOCK_METHOD(std::vector<std::string>, resolve, (std::string_view, std::string_view));
|
||||
};
|
||||
|
||||
testing::StrictMock<MockResolver> mockResolver;
|
||||
|
||||
constexpr static auto JSONDataIPV4 = R"JSON(
|
||||
{
|
||||
"dos_guard": {
|
||||
@@ -38,6 +53,43 @@ constexpr static auto JSONDataIPV4 = R"JSON(
|
||||
}
|
||||
)JSON";
|
||||
|
||||
EXPECT_CALL(mockResolver, resolve(testing::_, ""))
|
||||
.Times(3)
|
||||
.WillRepeatedly([](auto hostname, auto) -> std::vector<std::string> { return {std::string{hostname}}; });
|
||||
|
||||
Config const cfg{boost::json::parse(JSONDataIPV4)};
|
||||
WhitelistHandler const whitelistHandler{cfg, mockResolver};
|
||||
|
||||
EXPECT_TRUE(whitelistHandler.isWhiteListed("192.168.1.10"));
|
||||
EXPECT_FALSE(whitelistHandler.isWhiteListed("193.168.0.123"));
|
||||
EXPECT_TRUE(whitelistHandler.isWhiteListed("10.0.0.1"));
|
||||
EXPECT_FALSE(whitelistHandler.isWhiteListed("10.0.0.2"));
|
||||
}
|
||||
|
||||
TEST_F(WhitelistHandlerTest, TestWhiteListResolvesHostname)
|
||||
{
|
||||
constexpr static auto JSONDataIPV4 = R"JSON(
|
||||
{
|
||||
"dos_guard": {
|
||||
"whitelist": [
|
||||
"localhost",
|
||||
"10.0.0.1"
|
||||
]
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
Config const cfg{boost::json::parse(JSONDataIPV4)};
|
||||
WhitelistHandler const whitelistHandler{cfg};
|
||||
|
||||
EXPECT_TRUE(whitelistHandler.isWhiteListed("127.0.0.1"));
|
||||
EXPECT_FALSE(whitelistHandler.isWhiteListed("193.168.0.123"));
|
||||
EXPECT_TRUE(whitelistHandler.isWhiteListed("10.0.0.1"));
|
||||
EXPECT_FALSE(whitelistHandler.isWhiteListed("10.0.0.2"));
|
||||
}
|
||||
|
||||
TEST_F(WhitelistHandlerTest, TestWhiteListIPV6)
|
||||
{
|
||||
constexpr static auto JSONDataIPV6 = R"JSON(
|
||||
{
|
||||
"dos_guard": {
|
||||
@@ -49,21 +101,6 @@ constexpr static auto JSONDataIPV6 = R"JSON(
|
||||
}
|
||||
)JSON";
|
||||
|
||||
class WhitelistHandlerTest : public NoLoggerFixture {};
|
||||
|
||||
TEST_F(WhitelistHandlerTest, TestWhiteListIPV4)
|
||||
{
|
||||
Config const cfg{boost::json::parse(JSONDataIPV4)};
|
||||
WhitelistHandler const whitelistHandler{cfg};
|
||||
|
||||
EXPECT_TRUE(whitelistHandler.isWhiteListed("192.168.1.10"));
|
||||
EXPECT_FALSE(whitelistHandler.isWhiteListed("193.168.0.123"));
|
||||
EXPECT_TRUE(whitelistHandler.isWhiteListed("10.0.0.1"));
|
||||
EXPECT_FALSE(whitelistHandler.isWhiteListed("10.0.0.2"));
|
||||
}
|
||||
|
||||
TEST_F(WhitelistHandlerTest, TestWhiteListIPV6)
|
||||
{
|
||||
Config const cfg{boost::json::parse(JSONDataIPV6)};
|
||||
WhitelistHandler const whitelistHandler{cfg};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user