mirror of
https://github.com/XRPLF/clio.git
synced 2026-06-12 05:06:44 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14645e1494 | ||
|
|
ce44aec245 | ||
|
|
94da8459dd |
2
.github/scripts/conan/generate_matrix.py
vendored
2
.github/scripts/conan/generate_matrix.py
vendored
@@ -4,7 +4,7 @@ import json
|
||||
|
||||
LINUX_OS = ["heavy", "heavy-arm64"]
|
||||
LINUX_CONTAINERS = [
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
]
|
||||
LINUX_COMPILERS = ["gcc", "clang"]
|
||||
|
||||
|
||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
build_type: [Release, Debug]
|
||||
container:
|
||||
[
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }',
|
||||
'{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }',
|
||||
]
|
||||
static: [true]
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
conan_profile: gcc
|
||||
build_type: Debug
|
||||
download_ccache: true
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
needs: build-and-test
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996
|
||||
image: ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
4
.github/workflows/check-libxrpl.yml
vendored
4
.github/workflows/check-libxrpl.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
name: Build Clio / `libXRPL ${{ github.event.client_payload.version }}`
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996
|
||||
image: ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
needs: build
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996
|
||||
image: ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
|
||||
2
.github/workflows/clang-tidy.yml
vendored
2
.github/workflows/clang-tidy.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
if: github.event_name != 'push' || contains(github.event.head_commit.message, 'clang-tidy auto fixes')
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996
|
||||
image: ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996
|
||||
image: ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
10
.github/workflows/nightly.yml
vendored
10
.github/workflows/nightly.yml
vendored
@@ -55,17 +55,17 @@ jobs:
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
static: true
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
- os: heavy
|
||||
conan_profile: gcc
|
||||
build_type: Debug
|
||||
static: true
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
- os: heavy
|
||||
conan_profile: gcc.ubsan
|
||||
build_type: Release
|
||||
static: false
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
download_ccache: false
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
include:
|
||||
- os: heavy
|
||||
conan_profile: clang
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
static: true
|
||||
- os: macos15
|
||||
conan_profile: apple-clang
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -11,4 +11,4 @@ jobs:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@282890f46d6921249d5659dd38babcb0bd8aef48
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-pre-commit:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-pre-commit:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
static: true
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
conan_profile: gcc
|
||||
build_type: Release
|
||||
download_ccache: false
|
||||
|
||||
2
.github/workflows/reusable-release.yml
vendored
2
.github/workflows/reusable-release.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
release:
|
||||
runs-on: heavy
|
||||
container:
|
||||
image: ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996
|
||||
image: ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
2
.github/workflows/sanitizers.yml
vendored
2
.github/workflows/sanitizers.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
with:
|
||||
runs_on: heavy
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031" }'
|
||||
download_ccache: false
|
||||
upload_ccache: false
|
||||
conan_profile: ${{ matrix.compiler }}${{ matrix.sanitizer_ext }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
This image contains an environment to build [Clio](https://github.com/XRPLF/clio), check code and documentation.
|
||||
It is used in [Clio Github Actions](https://github.com/XRPLF/clio/actions) but can also be used to compile Clio locally.
|
||||
|
||||
The image is based on Ubuntu 20.04 and contains:
|
||||
The image is based on Ubuntu 22.04 and contains:
|
||||
|
||||
- ccache 4.12.2
|
||||
- Clang 19
|
||||
|
||||
@@ -10,3 +10,5 @@ os=Linux
|
||||
[conf]
|
||||
tools.build:compiler_executables={"c": "/usr/bin/clang-19", "cpp": "/usr/bin/clang++-19"}
|
||||
grpc/1.50.1:tools.build:cxxflags+=["-Wno-missing-template-arg-list-after-template-kw"]
|
||||
user.package:libc_version=2.32
|
||||
tools.info.package_id:confs+=["user.package:libc_version"]
|
||||
|
||||
@@ -9,3 +9,5 @@ os=Linux
|
||||
|
||||
[conf]
|
||||
tools.build:compiler_executables={"c": "/usr/bin/gcc-15", "cpp": "/usr/bin/g++-15"}
|
||||
user.package:libc_version=2.32
|
||||
tools.info.package_id:confs+=["user.package:libc_version"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
clio_develop:
|
||||
image: ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996
|
||||
image: ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031
|
||||
volumes:
|
||||
- clio_develop_conan_data:/root/.conan2/p
|
||||
- clio_develop_ccache:/root/.ccache
|
||||
|
||||
@@ -175,7 +175,7 @@ Open the `index.html` file in your browser to see the documentation pages.
|
||||
It is also possible to build Clio using [Docker](https://www.docker.com/) if you don't want to install all the dependencies on your machine.
|
||||
|
||||
```sh
|
||||
docker run -it ghcr.io/xrplf/clio-ci:6bb4953f1643b999781609ca79d5ec467289c996
|
||||
docker run -it ghcr.io/xrplf/clio-ci:94da8459ddc30e2f0d88a98cba63a57b2cda3031
|
||||
git clone https://github.com/XRPLF/clio
|
||||
cd clio
|
||||
```
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <boost/beast/http/field.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -63,20 +64,20 @@ ProxyIpResolver::fromConfig(util::config::ClioConfigDefinition const& config)
|
||||
return ProxyIpResolver{std::move(ips), std::move(tokens)};
|
||||
}
|
||||
|
||||
std::string
|
||||
std::optional<std::string>
|
||||
ProxyIpResolver::resolveClientIp(std::string const& connectionIp, HttpHeaders const& headers) const
|
||||
{
|
||||
if (proxyIps_.contains(connectionIp)) {
|
||||
return extractClientIp(headers).value_or(connectionIp);
|
||||
return extractClientIp(headers);
|
||||
}
|
||||
|
||||
if (auto it = headers.find(kPROXY_TOKEN_HEADER); it != headers.end()) {
|
||||
auto const tokenHash = util::sha256sum(it->value());
|
||||
if (proxyTokens_.contains(tokenHash)) {
|
||||
return extractClientIp(headers).value_or(connectionIp);
|
||||
return extractClientIp(headers);
|
||||
}
|
||||
}
|
||||
return connectionIp;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
@@ -92,14 +93,17 @@ ProxyIpResolver::extractClientIp(HttpHeaders const& headers)
|
||||
auto const headerValue = util::toLower(it->value());
|
||||
|
||||
static constexpr std::string_view kFOR_PREFIX = "for=";
|
||||
auto const startPos = headerValue.find(kFOR_PREFIX);
|
||||
auto const startPos = headerValue.rfind(kFOR_PREFIX);
|
||||
if (startPos == std::string::npos) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto value = it->value().substr(startPos + kFOR_PREFIX.size());
|
||||
|
||||
static constexpr char kDELIMITER = ';';
|
||||
auto const endPos = value.find(kDELIMITER);
|
||||
static constexpr char kSECTION_DELIMITER = ';';
|
||||
static constexpr char kCHAIN_DELIMITER = ',';
|
||||
auto const sectionEnd = value.find(kSECTION_DELIMITER);
|
||||
auto const chainEnd = value.find(kCHAIN_DELIMITER);
|
||||
auto const endPos = std::min(sectionEnd, chainEnd);
|
||||
auto const ip = value.substr(0, endPos);
|
||||
|
||||
static constexpr auto kMIN_IP_LENGTH = 7; // minimum 3 dots + 4 digits
|
||||
|
||||
@@ -75,16 +75,15 @@ public:
|
||||
*
|
||||
* If the connection IP is in the trusted proxy list, or if a valid proxy token is provided in the headers,
|
||||
* this method will attempt to extract the client's IP from the `Forwarded` header.
|
||||
* Otherwise, it returns the connection IP.
|
||||
* Otherwise, returns std::nullopt.
|
||||
*
|
||||
* @param connectionIp The IP address of the direct connection.
|
||||
* @param headers The HTTP request headers.
|
||||
* @return The resolved client IP address as a string.
|
||||
* @return The resolved client IP address if the connection is from a trusted proxy, otherwise std::nullopt.
|
||||
*/
|
||||
std::string
|
||||
std::optional<std::string>
|
||||
resolveClientIp(std::string const& connectionIp, HttpHeaders const& headers) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Extracts the client IP from the `Forwarded` HTTP header.
|
||||
*
|
||||
|
||||
@@ -43,8 +43,10 @@
|
||||
#include <boost/beast/http/error.hpp>
|
||||
#include <boost/beast/http/field.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/http/message_fwd.hpp>
|
||||
#include <boost/beast/http/status.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/string_body_fwd.hpp>
|
||||
#include <boost/beast/http/verb.hpp>
|
||||
#include <boost/beast/ssl.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
@@ -139,6 +141,7 @@ class HttpBase : public ConnectionBase {
|
||||
SendLambda sender_;
|
||||
std::shared_ptr<AdminVerificationStrategy> adminVerification_;
|
||||
std::shared_ptr<ProxyIpResolver> proxyIpResolver_;
|
||||
bool isProxyConnection_ = false;
|
||||
|
||||
protected:
|
||||
boost::beast::flat_buffer buffer_;
|
||||
@@ -239,6 +242,23 @@ public:
|
||||
if (ec)
|
||||
return httpFail(ec, "read");
|
||||
|
||||
auto const updateClientIp = [&](std::string newIp) {
|
||||
if (newIp == clientIp_)
|
||||
return;
|
||||
LOG(log_.info()) << tag() << "Detected a forwarded request from proxy. Resolved client ip: " << newIp;
|
||||
dosGuard_.get().decrement(clientIp_);
|
||||
clientIp_ = std::move(newIp);
|
||||
dosGuard_.get().increment(clientIp_);
|
||||
};
|
||||
|
||||
if (isProxyConnection_) {
|
||||
if (auto resolvedIp = ProxyIpResolver::extractClientIp(req_); resolvedIp.has_value())
|
||||
updateClientIp(std::move(*resolvedIp));
|
||||
} else if (auto resolvedIp = proxyIpResolver_->resolveClientIp(clientIp_, req_); resolvedIp.has_value()) {
|
||||
updateClientIp(std::move(*resolvedIp));
|
||||
isProxyConnection_ = true;
|
||||
}
|
||||
|
||||
if (req_.method() == http::verb::get and req_.target() == "/health")
|
||||
return sender_(httpResponse(http::status::ok, "text/html", kHEALTH_CHECK_HTML));
|
||||
|
||||
@@ -249,14 +269,6 @@ public:
|
||||
return sender_(httpResponse(http::status::service_unavailable, "text/html", kCACHE_CHECK_NOT_LOADED_HTML));
|
||||
}
|
||||
|
||||
if (auto resolvedIp = proxyIpResolver_->resolveClientIp(clientIp_, req_); resolvedIp != clientIp_) {
|
||||
LOG(log_.info()) << tag() << "Detected a forwarded request from proxy. Proxy ip: " << clientIp_
|
||||
<< ". Resolved client ip: " << resolvedIp;
|
||||
dosGuard_.get().decrement(clientIp_);
|
||||
clientIp_ = std::move(resolvedIp);
|
||||
dosGuard_.get().increment(clientIp_);
|
||||
}
|
||||
|
||||
// Update isAdmin property of the connection
|
||||
ConnectionBase::isAdmin_ = adminVerification_->isAdmin(req_, clientIp_);
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ class ConnectionMetadata : public util::Taggable {
|
||||
protected:
|
||||
std::string ip_; // client ip
|
||||
std::optional<bool> isAdmin_;
|
||||
bool isProxyConnection_ = false;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -82,6 +83,26 @@ public:
|
||||
ip_ = std::move(newIp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mark this connection as coming through a trusted proxy.
|
||||
*/
|
||||
void
|
||||
markAsProxyConnection()
|
||||
{
|
||||
isProxyConnection_ = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Whether this connection was identified as coming through a trusted proxy.
|
||||
*
|
||||
* @return true if the connection is a proxy connection.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isProxyConnection() const
|
||||
{
|
||||
return isProxyConnection_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get whether the client is an admin.
|
||||
*
|
||||
|
||||
@@ -395,12 +395,22 @@ ConnectionHandler::handleRequest(
|
||||
void
|
||||
ConnectionHandler::resolveClientIp(Connection& connection, Request const& request) const
|
||||
{
|
||||
if (auto resolvedClientIp = proxyIpResolver_.resolveClientIp(connection.ip(), request.httpHeaders());
|
||||
resolvedClientIp != connection.ip()) {
|
||||
LOG(log_.info()) << connection.tag() << "Detected a forwarded request from proxy. Proxy ip: " << connection.ip()
|
||||
<< ". Resolved client ip: " << resolvedClientIp;
|
||||
onIpChangeHook_(connection.ip(), resolvedClientIp);
|
||||
connection.setIp(std::move(resolvedClientIp));
|
||||
auto const updateIp = [&](std::string newIp) {
|
||||
if (newIp == connection.ip())
|
||||
return;
|
||||
LOG(log_.info()) << connection.tag()
|
||||
<< "Detected a forwarded request from proxy. Resolved client ip: " << newIp;
|
||||
onIpChangeHook_(connection.ip(), newIp);
|
||||
connection.setIp(std::move(newIp));
|
||||
};
|
||||
|
||||
if (connection.isProxyConnection()) {
|
||||
if (auto resolvedIp = ProxyIpResolver::extractClientIp(request.httpHeaders()); resolvedIp.has_value())
|
||||
updateIp(std::move(*resolvedIp));
|
||||
} else if (auto resolvedIp = proxyIpResolver_.resolveClientIp(connection.ip(), request.httpHeaders());
|
||||
resolvedIp.has_value()) {
|
||||
updateIp(std::move(*resolvedIp));
|
||||
connection.markAsProxyConnection();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <fmt/format.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
@@ -44,7 +45,7 @@ struct ProxyIpResolverTestParams {
|
||||
std::unordered_set<std::string> proxyTokens;
|
||||
std::vector<std::pair<std::string, std::string>> headers;
|
||||
std::string connectionIp;
|
||||
std::string expectedIp;
|
||||
std::optional<std::string> expectedIp;
|
||||
};
|
||||
|
||||
class ProxyIpResolverTest : public ::testing::TestWithParam<ProxyIpResolverTestParams> {};
|
||||
@@ -79,11 +80,11 @@ TEST_F(ProxyIpResolverTest, FromConfig)
|
||||
auto const proxyIpResolver = ProxyIpResolver::fromConfig(config);
|
||||
ProxyIpResolver::HttpHeaders headers;
|
||||
|
||||
EXPECT_EQ(proxyIpResolver.resolveClientIp(clientIp, headers), clientIp);
|
||||
EXPECT_EQ(proxyIpResolver.resolveClientIp(proxyIp, headers), proxyIp);
|
||||
EXPECT_EQ(proxyIpResolver.resolveClientIp(clientIp, headers), std::nullopt);
|
||||
EXPECT_EQ(proxyIpResolver.resolveClientIp(proxyIp, headers), std::nullopt);
|
||||
|
||||
headers.set(boost::beast::http::field::forwarded, fmt::format("for={}", clientIp));
|
||||
EXPECT_EQ(proxyIpResolver.resolveClientIp(clientIp, headers), clientIp);
|
||||
EXPECT_EQ(proxyIpResolver.resolveClientIp(clientIp, headers), std::nullopt);
|
||||
EXPECT_EQ(proxyIpResolver.resolveClientIp(proxyIp, headers), clientIp);
|
||||
|
||||
headers.set(ProxyIpResolver::kPROXY_TOKEN_HEADER, proxyToken);
|
||||
@@ -113,7 +114,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
.proxyTokens = {},
|
||||
.headers = {},
|
||||
.connectionIp = "1.2.3.4",
|
||||
.expectedIp = "1.2.3.4"
|
||||
.expectedIp = std::nullopt
|
||||
},
|
||||
ProxyIpResolverTestParams{
|
||||
.testName = "TrustedProxyIpWithForwardedHeader",
|
||||
@@ -129,7 +130,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
.proxyTokens = {},
|
||||
.headers = {},
|
||||
.connectionIp = "5.6.7.8",
|
||||
.expectedIp = "5.6.7.8"
|
||||
.expectedIp = std::nullopt
|
||||
},
|
||||
ProxyIpResolverTestParams{
|
||||
.testName = "UntrustedProxyIpWithForwardedHeader",
|
||||
@@ -137,7 +138,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
.proxyTokens = {},
|
||||
.headers = {{std::string(http::to_string(http::field::forwarded)), "for=1.2.3.4"}},
|
||||
.connectionIp = "5.6.7.8",
|
||||
.expectedIp = "5.6.7.8"
|
||||
.expectedIp = std::nullopt
|
||||
},
|
||||
ProxyIpResolverTestParams{
|
||||
.testName = "TrustedProxyTokenWithForwardedHeader",
|
||||
@@ -155,7 +156,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
.proxyTokens = {"test_token"},
|
||||
.headers = {{std::string(ProxyIpResolver::kPROXY_TOKEN_HEADER), "test_token"}},
|
||||
.connectionIp = "5.6.7.8",
|
||||
.expectedIp = "5.6.7.8"
|
||||
.expectedIp = std::nullopt
|
||||
},
|
||||
ProxyIpResolverTestParams{
|
||||
.testName = "UntrustedProxyTokenWithForwardedHeader",
|
||||
@@ -165,7 +166,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
{{std::string(ProxyIpResolver::kPROXY_TOKEN_HEADER), "test_token"},
|
||||
{std::string(http::to_string(http::field::forwarded)), "for=1.2.3.4"}},
|
||||
.connectionIp = "5.6.7.8",
|
||||
.expectedIp = "5.6.7.8"
|
||||
.expectedIp = std::nullopt
|
||||
},
|
||||
ProxyIpResolverTestParams{
|
||||
.testName = "ForwardedHeaderWithAdditionalFields",
|
||||
@@ -191,7 +192,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
.proxyTokens = {},
|
||||
.headers = {{std::string(http::to_string(http::field::forwarded)), "by=1.2.3.4"}},
|
||||
.connectionIp = "5.6.7.8",
|
||||
.expectedIp = "5.6.7.8"
|
||||
.expectedIp = std::nullopt
|
||||
},
|
||||
ProxyIpResolverTestParams{
|
||||
.testName = "ForwardedHeaderWithIpInQuotes",
|
||||
@@ -207,7 +208,25 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
.proxyTokens = {},
|
||||
.headers = {{std::string(http::to_string(http::field::forwarded)), "for=\";some_other_text"}},
|
||||
.connectionIp = "5.6.7.8",
|
||||
.expectedIp = "5.6.7.8"
|
||||
.expectedIp = std::nullopt
|
||||
},
|
||||
ProxyIpResolverTestParams{
|
||||
.testName = "ForwardedHeaderWithMultipleForValues",
|
||||
.proxyIps = {"5.6.7.8"},
|
||||
.proxyTokens = {},
|
||||
.headers = {{std::string(http::to_string(http::field::forwarded)), "for=1.2.3.4, for=9.10.11.12"}},
|
||||
.connectionIp = "5.6.7.8",
|
||||
.expectedIp = "9.10.11.12"
|
||||
},
|
||||
ProxyIpResolverTestParams{
|
||||
.testName = "ForwardedHeaderWithMultipleForValuesAndSectionDelimiters",
|
||||
.proxyIps = {"5.6.7.8"},
|
||||
.proxyTokens = {},
|
||||
.headers =
|
||||
{{std::string(http::to_string(http::field::forwarded)),
|
||||
"for=1.2.3.4; proto=http, for=9.10.11.12; proto=https"}},
|
||||
.connectionIp = "5.6.7.8",
|
||||
.expectedIp = "9.10.11.12"
|
||||
}
|
||||
),
|
||||
tests::util::kNAME_GENERATOR
|
||||
|
||||
@@ -523,6 +523,88 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, OnIpChangeHookCalledWhenSentFr
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, ProxyConnection_SameClientReuses_HookCalledOnce)
|
||||
{
|
||||
std::string const target = "/some/target";
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
getHandlerMock;
|
||||
connectionHandler.onGet(target, getHandlerMock.AsStdFunction());
|
||||
|
||||
StrictMockHttpConnectionPtr mockProxyConnection =
|
||||
std::make_unique<StrictMockHttpConnection>(proxyIp, boost::beast::flat_buffer{}, tagDecoratorFactory);
|
||||
|
||||
auto request = http::request<http::string_body>{http::verb::get, target, 11, ""};
|
||||
request.set(http::field::forwarded, fmt::format("for={}", clientIp));
|
||||
|
||||
EXPECT_CALL(*mockProxyConnection, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockProxyConnection, receive)
|
||||
.WillOnce(Return(makeRequest(request)))
|
||||
.WillOnce(Return(makeRequest(request)))
|
||||
.WillOnce(Return(makeError(http::error::end_of_stream)));
|
||||
|
||||
EXPECT_CALL(onIpChangeMock, Call(proxyIp, clientIp));
|
||||
|
||||
EXPECT_CALL(getHandlerMock, Call).Times(2).WillRepeatedly([](Request const& req, auto&&, auto&&, auto&&) {
|
||||
return Response(http::status::ok, "ok", req);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockProxyConnection, send).Times(2).WillRepeatedly(Return(std::expected<void, web::ng::Error>{}));
|
||||
|
||||
EXPECT_CALL(onDisconnectMock, Call).WillOnce([this, ptr = mockProxyConnection.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, ptr);
|
||||
EXPECT_EQ(c.ip(), clientIp);
|
||||
});
|
||||
|
||||
runSpawn([this, c = std::move(mockProxyConnection)](boost::asio::yield_context yield) mutable {
|
||||
connectionHandler.processConnection(std::move(c), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, ProxyConnection_DifferentClientReuses_HookCalledForEachIpChange)
|
||||
{
|
||||
std::string const target = "/some/target";
|
||||
std::string const anotherClientIp = "9.10.11.12";
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
getHandlerMock;
|
||||
connectionHandler.onGet(target, getHandlerMock.AsStdFunction());
|
||||
|
||||
StrictMockHttpConnectionPtr mockProxyConnection =
|
||||
std::make_unique<StrictMockHttpConnection>(proxyIp, boost::beast::flat_buffer{}, tagDecoratorFactory);
|
||||
|
||||
auto request1 = http::request<http::string_body>{http::verb::get, target, 11, ""};
|
||||
request1.set(http::field::forwarded, fmt::format("for={}", clientIp));
|
||||
|
||||
auto request2 = http::request<http::string_body>{http::verb::get, target, 11, ""};
|
||||
request2.set(http::field::forwarded, fmt::format("for={}", anotherClientIp));
|
||||
|
||||
EXPECT_CALL(*mockProxyConnection, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockProxyConnection, receive)
|
||||
.WillOnce(Return(makeRequest(request1)))
|
||||
.WillOnce(Return(makeRequest(request2)))
|
||||
.WillOnce(Return(makeError(http::error::end_of_stream)));
|
||||
|
||||
EXPECT_CALL(onIpChangeMock, Call(proxyIp, clientIp));
|
||||
EXPECT_CALL(onIpChangeMock, Call(clientIp, anotherClientIp));
|
||||
|
||||
EXPECT_CALL(getHandlerMock, Call).Times(2).WillRepeatedly([](Request const& req, auto&&, auto&&, auto&&) {
|
||||
return Response(http::status::ok, "ok", req);
|
||||
});
|
||||
|
||||
EXPECT_CALL(*mockProxyConnection, send).Times(2).WillRepeatedly(Return(std::expected<void, web::ng::Error>{}));
|
||||
|
||||
EXPECT_CALL(onDisconnectMock, Call)
|
||||
.WillOnce([anotherClientIp, ptr = mockProxyConnection.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, ptr);
|
||||
EXPECT_EQ(c.ip(), anotherClientIp);
|
||||
});
|
||||
|
||||
runSpawn([this, c = std::move(mockProxyConnection)](boost::asio::yield_context yield) mutable {
|
||||
connectionHandler.processConnection(std::move(c), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, Stop)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
@@ -728,6 +810,75 @@ TEST_F(ConnectionHandlerParallelProcessingTest, OnIpChangeHookCalledWhenSentFrom
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerParallelProcessingTest, ProxyConnection_SameClientReuses_HookCalledOnce)
|
||||
{
|
||||
connectionHandler.onWs([](Request const& req, auto&&, auto&&, auto&&) {
|
||||
return Response(http::status::ok, "ok", req);
|
||||
});
|
||||
|
||||
StrictMockWsConnectionPtr mockProxyConnection =
|
||||
std::make_unique<StrictMockWsConnection>(proxyIp, boost::beast::flat_buffer{}, tagDecoratorFactory);
|
||||
|
||||
headers.set(http::field::forwarded, fmt::format("for={}", clientIp));
|
||||
|
||||
EXPECT_CALL(*mockProxyConnection, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockProxyConnection, receive)
|
||||
.WillOnce(Return(makeRequest("msg", headers)))
|
||||
.WillOnce(Return(makeRequest("msg", headers)))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(onIpChangeMock, Call(proxyIp, clientIp));
|
||||
|
||||
EXPECT_CALL(*mockProxyConnection, send).Times(2).WillRepeatedly(Return(std::expected<void, web::ng::Error>{}));
|
||||
|
||||
EXPECT_CALL(onDisconnectMock, Call).WillOnce([this, ptr = mockProxyConnection.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, ptr);
|
||||
EXPECT_EQ(c.ip(), clientIp);
|
||||
});
|
||||
|
||||
runSpawn([this, c = std::move(mockProxyConnection)](boost::asio::yield_context yield) mutable {
|
||||
connectionHandler.processConnection(std::move(c), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerParallelProcessingTest, ProxyConnection_DifferentClientReuses_HookCalledForEachIpChange)
|
||||
{
|
||||
std::string const anotherClientIp = "9.10.11.12";
|
||||
connectionHandler.onWs([](Request const& req, auto&&, auto&&, auto&&) {
|
||||
return Response(http::status::ok, "ok", req);
|
||||
});
|
||||
|
||||
StrictMockWsConnectionPtr mockProxyConnection =
|
||||
std::make_unique<StrictMockWsConnection>(proxyIp, boost::beast::flat_buffer{}, tagDecoratorFactory);
|
||||
|
||||
Request::HttpHeaders headers1;
|
||||
headers1.set(http::field::forwarded, fmt::format("for={}", clientIp));
|
||||
|
||||
Request::HttpHeaders headers2;
|
||||
headers2.set(http::field::forwarded, fmt::format("for={}", anotherClientIp));
|
||||
|
||||
EXPECT_CALL(*mockProxyConnection, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockProxyConnection, receive)
|
||||
.WillOnce(Return(makeRequest("msg", headers1)))
|
||||
.WillOnce(Return(makeRequest("msg", headers2)))
|
||||
.WillOnce(Return(makeError(websocket::error::closed)));
|
||||
|
||||
EXPECT_CALL(onIpChangeMock, Call(proxyIp, clientIp));
|
||||
EXPECT_CALL(onIpChangeMock, Call(clientIp, anotherClientIp));
|
||||
|
||||
EXPECT_CALL(*mockProxyConnection, send).Times(2).WillRepeatedly(Return(std::expected<void, web::ng::Error>{}));
|
||||
|
||||
EXPECT_CALL(onDisconnectMock, Call)
|
||||
.WillOnce([anotherClientIp, ptr = mockProxyConnection.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, ptr);
|
||||
EXPECT_EQ(c.ip(), anotherClientIp);
|
||||
});
|
||||
|
||||
runSpawn([this, c = std::move(mockProxyConnection)](boost::asio::yield_context yield) mutable {
|
||||
connectionHandler.processConnection(std::move(c), yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerParallelProcessingTest, Receive_Handle_Send_Loop)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
|
||||
Reference in New Issue
Block a user