Feature #7586 » 0001-Issue-7586-configurable-trusted-proxies-and-forwarde.patch
| examples/widgetgallery/approot/wt_config.xml | ||
|---|---|---|
|
</user-agents>
|
||
|
<web-sockets>true</web-sockets>
|
||
|
<behind-reverse-proxy>true</behind-reverse-proxy>
|
||
|
<forwarded-header>X-Forwarded-For</forwarded-header>
|
||
|
<trusted-proxies>127.0.0.1,::1</trusted-proxies>
|
||
|
<bootstrap-method>
|
||
|
<for path="*">progressive</for>
|
||
|
<for path="/forms/line_text-editor">default</for>
|
||
| src/Wt/Http/Request.C | ||
|---|---|---|
|
return std::string();
|
||
|
WServer *server = WServer::instance();
|
||
|
const bool behindReverseProxy = server && server->configuration().behindReverseProxy();
|
||
|
return request_->clientAddress(behindReverseProxy);
|
||
|
return request_->clientAddress(server->configuration());
|
||
|
}
|
||
|
WSslInfo *Request::sslInfo() const
|
||
| ... | ... | |
|
return sslInfo_.get();
|
||
|
if (request_) {
|
||
|
auto server = WServer::instance();
|
||
|
bool behindReverseProxy = server && server->configuration().behindReverseProxy();
|
||
|
sslInfo_ = request_->sslInfo(behindReverseProxy);
|
||
|
sslInfo_ = request_->sslInfo(server->configuration());
|
||
|
}
|
||
|
return sslInfo_.get();
|
||
|
}
|
||
| src/Wt/WEnvironment.C | ||
|---|---|---|
|
std::string oldHost = host_;
|
||
|
host_ = str(request.headerValue("Host"));
|
||
|
if(conf.behindReverseProxy()) {
|
||
|
if (conf.isTrustedProxy(request.remoteAddr())) {
|
||
|
std::string forwardedHost = str(request.headerValue("X-Forwarded-Host"));
|
||
|
if (!forwardedHost.empty()) {
|
||
| ... | ... | |
|
urlScheme_ = str(request.urlScheme());
|
||
|
Configuration& conf = session_->controller()->configuration();
|
||
|
#ifndef WT_TARGET_JAVA
|
||
|
if (conf.behindReverseProxy() || server()->dedicatedSessionProcess()) {
|
||
|
#else
|
||
|
if (conf.behindReverseProxy()){
|
||
|
#endif
|
||
|
std::string forwardedProto = str(request.headerValue("X-Forwarded-Proto"));
|
||
|
if (!forwardedProto.empty()) {
|
||
|
std::string::size_type i = forwardedProto.rfind(',');
|
||
|
if (i == std::string::npos)
|
||
|
urlScheme_ = forwardedProto;
|
||
|
else
|
||
|
urlScheme_ = forwardedProto.substr(i+1);
|
||
|
}
|
||
|
if (conf.isTrustedProxy(request.remoteAddr())) {
|
||
|
std::string forwardedProto = str(request.headerValue("X-Forwarded-Proto"));
|
||
|
if (!forwardedProto.empty()) {
|
||
|
std::string::size_type i = forwardedProto.rfind(',');
|
||
|
if (i == std::string::npos)
|
||
|
urlScheme_ = forwardedProto;
|
||
|
else
|
||
|
urlScheme_ = forwardedProto.substr(i+1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
| ... | ... | |
|
if(!str(request.headerValue("Redirect-Secret")).empty())
|
||
|
session_->controller()->redirectSecret_ = str(request.headerValue("Redirect-Secret"));
|
||
|
sslInfo_ = request.sslInfo(conf.behindReverseProxy());
|
||
|
sslInfo_ = request.sslInfo(conf);
|
||
|
#endif
|
||
|
setUserAgent(str(request.headerValue("User-Agent")));
|
||
| ... | ... | |
|
* If behind a reverse proxy, use external host, schema as communicated using 'X-Forwarded'
|
||
|
* headers.
|
||
|
*/
|
||
|
#ifndef WT_TARGET_JAVA
|
||
|
if (conf.behindReverseProxy() || server()->dedicatedSessionProcess()) {
|
||
|
#else
|
||
|
if (conf.behindReverseProxy()){
|
||
|
#endif
|
||
|
if (conf.isTrustedProxy(request.remoteAddr())) {
|
||
|
std::string forwardedHost = str(request.headerValue("X-Forwarded-Host"));
|
||
|
if (!forwardedHost.empty()) {
|
||
| ... | ... | |
|
host_ += ":" + request.serverPort();
|
||
|
}
|
||
|
clientAddress_ = request.clientAddress(conf.behindReverseProxy());
|
||
|
clientAddress_ = request.clientAddress(conf);
|
||
|
const char *cookie = request.headerValue("Cookie");
|
||
|
doesCookies_ = cookie;
|
||
| src/fcgi/FCGIStream.C | ||
|---|---|---|
|
return true;
|
||
|
}
|
||
|
virtual std::unique_ptr<WSslInfo> sslInfo(bool) const override
|
||
|
virtual std::unique_ptr<WSslInfo> sslInfo(const Wt::Configuration &) const override
|
||
|
{
|
||
|
#ifdef WT_WITH_SSL
|
||
|
std::string clientCert = str(envValue("SSL_CLIENT_CERT"));
|
||
| src/http/HTTPRequest.C | ||
|---|---|---|
|
return false;
|
||
|
}
|
||
|
std::unique_ptr<Wt::WSslInfo> HTTPRequest::sslInfo(bool behindReverseProxy) const
|
||
|
std::unique_ptr<Wt::WSslInfo> HTTPRequest::sslInfo(const Wt::Configuration &conf) const
|
||
|
{
|
||
|
auto result = reply_->request().sslInfo();
|
||
|
if (behindReverseProxy) {
|
||
|
if (conf.isTrustedProxy(remoteAddr())) {
|
||
|
#ifdef HTTP_WITH_SSL
|
||
|
if (!result)
|
||
|
result = sslInfoFromJson();
|
||
| src/http/HTTPRequest.h | ||
|---|---|---|
|
virtual const std::string& remoteAddr() const override;
|
||
|
virtual const char *urlScheme() const override;
|
||
|
bool isSynchronous() const;
|
||
|
virtual std::unique_ptr<Wt::WSslInfo> sslInfo(bool behindReverseProxy) const override;
|
||
|
virtual std::unique_ptr<Wt::WSslInfo> sslInfo(const Wt::Configuration & conf) const override;
|
||
|
virtual const std::vector<std::pair<std::string, std::string> > &urlParams() const override;
|
||
|
private:
|
||
| src/http/ProxyReply.C | ||
|---|---|---|
|
std::string forwardedFor;
|
||
|
std::string forwardedProto = request_.urlScheme;
|
||
|
std::string forwardedPort;
|
||
|
std::string forwardedHost;
|
||
|
const Wt::Configuration& wtConfiguration
|
||
|
= connection()->server()->controller()->configuration();
|
||
|
const bool trustedProxy = wtConfiguration.isTrustedProxy(request_.remoteIP);
|
||
|
for (Request::HeaderList::const_iterator it = request_.headers.begin();
|
||
|
it != request_.headers.end(); ++it) {
|
||
|
if (it->name.iequals("Connection") || it->name.iequals("Keep-Alive") ||
|
||
| ... | ... | |
|
"requests to a child process. Maybe someone is trying to spoof this "
|
||
|
"header?");
|
||
|
} else if (it->name.istarts_with("X-SSL-Client-")) {
|
||
|
if (wtConfiguration.behindReverseProxy()) {
|
||
|
if (trustedProxy) {
|
||
|
os << it->name << ": " << it->value << "\r\n";
|
||
|
} else {
|
||
|
LOG_SECURE("wthttp is not behind a reverse proxy, dropping " << it->name.str() << " header");
|
||
|
LOG_SECURE("wthttp is not behind a trusted reverse proxy, dropping " << it->name.str() << " header");
|
||
|
}
|
||
|
} else if (it->name.iequals("X-Forwarded-For") ||
|
||
|
it->name.iequals("Client-IP")) {
|
||
|
if (wtConfiguration.behindReverseProxy()) {
|
||
|
} else if (it->name.iequals(wtConfiguration.forwardedHeader().c_str())) {
|
||
|
if (trustedProxy) {
|
||
|
forwardedFor = it->value.str() + ", ";
|
||
|
} else {
|
||
|
LOG_SECURE("wthttp is not behind a trusted reverse proxy, dropping " << it->name.str() << " header");
|
||
|
}
|
||
|
} else if (it->name.iequals("Upgrade")) {
|
||
|
if (it->value.iequals("websocket")) {
|
||
|
establishWebSockets = true;
|
||
|
}
|
||
|
} else if (it->name.iequals("X-Forwarded-Proto")) {
|
||
|
if (wtConfiguration.behindReverseProxy()) {
|
||
|
if (trustedProxy) {
|
||
|
forwardedProto = it->value.str();
|
||
|
} else {
|
||
|
LOG_SECURE("wthttp is not behind a trusted reverse proxy, dropping " << it->name.str() << " header");
|
||
|
}
|
||
|
} else if(it->name.iequals("X-Forwarded-Port")) {
|
||
|
if (wtConfiguration.behindReverseProxy()) {
|
||
|
if (trustedProxy) {
|
||
|
forwardedPort = it->value.str();
|
||
|
} else {
|
||
|
LOG_SECURE("wthttp is not behind a trusted reverse proxy, dropping " << it->name.str() << " header");
|
||
|
}
|
||
|
} else if (it->name.iequals("X-Forwarded-Host")) {
|
||
|
if (trustedProxy) {
|
||
|
forwardedHost = it->value.str();
|
||
|
} else {
|
||
|
LOG_SECURE("wthttp is not behind a trusted reverse proxy, dropping " << it->name.str() << " header");
|
||
|
}
|
||
|
} else if (it->name.length() > 0) {
|
||
|
os << it->name << ": " << it->value << "\r\n";
|
||
| ... | ... | |
|
os << "X-Forwarded-Port: " << forwardedPort << "\r\n";
|
||
|
else
|
||
|
os << "X-Forwarded-Port: " << request_.port << "\r\n";
|
||
|
if (!forwardedHost.empty())
|
||
|
os << "X-Forwarded-Host: " << forwardedHost << "\r\n";
|
||
|
// Forward SSL Certificate to session only for first request
|
||
|
if (fwCertificates_) {
|
||
|
auto sslInfo = request_.sslInfo();
|
||
| src/http/WServer.C | ||
|---|---|---|
|
if (impl_->serverConfiguration_->parentPort() != -1) {
|
||
|
configuration().setBehindReverseProxy(true);
|
||
|
configuration().setForwardedHeader("X-Forwarded-For");
|
||
|
configuration().setTrustedProxies({
|
||
|
Configuration::Network::fromString("127.0.0.1"),
|
||
|
Configuration::Network::fromString("::1")
|
||
|
});
|
||
|
dedicatedProcessEnabled_ = true;
|
||
|
}
|
||
| src/isapi/IsapiRequest.C | ||
|---|---|---|
|
return "http";
|
||
|
}
|
||
|
std::unique_ptr<WSslInfo> IsapiRequest::sslInfo(bool) const {
|
||
|
std::unique_ptr<WSslInfo> IsapiRequest::sslInfo(const Configuration &) const {
|
||
|
#ifdef WT_WITH_SSL
|
||
|
CERT_CONTEXT_EX cce;
|
||
|
memset(&cce, 0, sizeof(CERT_CONTEXT_EX));
|
||
| src/isapi/IsapiRequest.h | ||
|---|---|---|
|
virtual const char *urlScheme() const;
|
||
|
virtual std::unique_ptr<WSslInfo> sslInfo(bool behindReverseProxy) const;
|
||
|
virtual std::unique_ptr<WSslInfo> sslInfo(const Configuration &conf) const;
|
||
|
private:
|
||
|
LPEXTENSION_CONTROL_BLOCK ecb_;
|
||
| src/web/Configuration.C | ||
|---|---|---|
|
#include <sys/stat.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <algorithm>
|
||
|
#include <array>
|
||
|
#include <vector>
|
||
|
#include <iostream>
|
||
|
#include <fstream>
|
||
|
#include <stdlib.h>
|
||
|
#include <regex>
|
||
|
#include <Wt/AsioWrapper/system_error.hpp>
|
||
|
#include "3rdparty/rapidxml/rapidxml.hpp"
|
||
|
#include "3rdparty/rapidxml/rapidxml_print.hpp"
|
||
| ... | ... | |
|
return result;
|
||
|
}
|
||
|
template<std::size_t N>
|
||
|
bool prefixMatches(const std::array<unsigned char, N> &networkBytes,
|
||
|
const std::array<unsigned char, N> &addressBytes,
|
||
|
const unsigned char prefixLength)
|
||
|
{
|
||
|
for (std::size_t i = 0; i < N; ++i) {
|
||
|
if ((i + 1) * 8 < prefixLength) {
|
||
|
if (networkBytes[i] != addressBytes[i]) {
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
const unsigned char shift = (i + 1) * 8 - prefixLength;
|
||
|
return (networkBytes[i] >> shift) == (addressBytes[i] >> shift);
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
static std::string FORWARD_SLASH = "/";
|
||
|
}
|
||
| ... | ... | |
|
userAgent_(userAgent)
|
||
|
{ }
|
||
|
Configuration::Network Configuration::Network::fromString(const std::string &s)
|
||
|
{
|
||
|
const auto slashPos = s.find('/');
|
||
|
if (slashPos == std::string::npos) {
|
||
|
AsioWrapper::error_code ec;
|
||
|
const auto address = AsioWrapper::asio::ip::address::from_string(s, ec);
|
||
|
if (ec) {
|
||
|
throw std::invalid_argument("'" + s + "' is not a valid IP address");
|
||
|
}
|
||
|
const unsigned char prefixLength = address.is_v6() ? 128 : 32;
|
||
|
return Network { address, prefixLength };
|
||
|
} else {
|
||
|
AsioWrapper::error_code ec;
|
||
|
const auto address = AsioWrapper::asio::ip::address::from_string(s.substr(0, slashPos), ec);
|
||
|
if (ec) {
|
||
|
throw std::invalid_argument("'" + s + "' is not a valid IP address");
|
||
|
}
|
||
|
const unsigned int prefixLength = Utils::stoi(s.substr(slashPos + 1));
|
||
|
if (prefixLength < 0 ||
|
||
|
(address.is_v4() && prefixLength > 32) ||
|
||
|
(address.is_v6() && prefixLength > 128)) {
|
||
|
throw std::invalid_argument("Invalid prefix length " + s.substr(slashPos + 1) + " for IPv" +
|
||
|
std::string(address.is_v4() ? "4" : "6") + " address");
|
||
|
}
|
||
|
return Network { address, static_cast<unsigned char>(prefixLength) };
|
||
|
}
|
||
|
}
|
||
|
bool Configuration::Network::contains(const AsioWrapper::asio::ip::address &address) const
|
||
|
{
|
||
|
if (this->address.is_v6() && address.is_v6()) {
|
||
|
const auto networkBytes = this->address.to_v6().to_bytes();
|
||
|
const auto addressBytes = address.to_v6().to_bytes();
|
||
|
return prefixMatches(networkBytes, addressBytes, prefixLength);
|
||
|
} else if (this->address.is_v4() && address.is_v4()) {
|
||
|
const auto networkBytes = this->address.to_v4().to_bytes();
|
||
|
const auto addressBytes = address.to_v4().to_bytes();
|
||
|
return prefixMatches(networkBytes, addressBytes, prefixLength);
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
Configuration::Configuration(const std::string& applicationPath,
|
||
|
const std::string& appRoot,
|
||
|
const std::string& configurationFile,
|
||
| ... | ... | |
|
properties_.clear();
|
||
|
xhtmlMimeType_ = false;
|
||
|
behindReverseProxy_ = false;
|
||
|
forwardedHeader_ = "X-Forwarded-For";
|
||
|
trustedProxies_ = {
|
||
|
Network::fromString("10.0.0.0/8"),
|
||
|
Network::fromString("172.16.0.0/12"),
|
||
|
Network::fromString("192.168.0.0/16"),
|
||
|
Network::fromString("fd00::/8"),
|
||
|
Network::fromString("127.0.0.1/32"),
|
||
|
Network::fromString("::1/128")
|
||
|
};
|
||
|
redirectMsg_ = "Load basic HTML";
|
||
|
serializedEvents_ = false;
|
||
|
webSockets_ = false;
|
||
| ... | ... | |
|
return behindReverseProxy_;
|
||
|
}
|
||
|
std::string Configuration::forwardedHeader() const {
|
||
|
READ_LOCK;
|
||
|
return forwardedHeader_;
|
||
|
}
|
||
|
std::vector<Configuration::Network> Configuration::trustedProxies() const {
|
||
|
READ_LOCK;
|
||
|
return trustedProxies_;
|
||
|
}
|
||
|
bool Configuration::isTrustedProxy(const std::string &ipAddress) const {
|
||
|
READ_LOCK;
|
||
|
if (behindReverseProxy_) {
|
||
|
return false;
|
||
|
}
|
||
|
AsioWrapper::error_code ec;
|
||
|
const auto address = AsioWrapper::asio::ip::address::from_string(ipAddress, ec);
|
||
|
if (ec) {
|
||
|
return false;
|
||
|
}
|
||
|
return std::any_of(begin(trustedProxies_), end(trustedProxies_), [&address](const Network &network) {
|
||
|
return network.contains(address);
|
||
|
});
|
||
|
}
|
||
|
std::string Configuration::redirectMessage() const
|
||
|
{
|
||
|
READ_LOCK;
|
||
| ... | ... | |
|
behindReverseProxy_ = enabled;
|
||
|
}
|
||
|
void Configuration::setForwardedHeader(const std::string &forwardedHeader)
|
||
|
{
|
||
|
forwardedHeader_ = forwardedHeader;
|
||
|
}
|
||
|
void Configuration::setTrustedProxies(const std::vector<Network> &trustedProxies)
|
||
|
{
|
||
|
trustedProxies_ = trustedProxies;
|
||
|
}
|
||
|
void Configuration::readApplicationSettings(xml_node<> *app)
|
||
|
{
|
||
|
xml_node<> *sess = singleChildElement(app, "session-management");
|
||
| ... | ... | |
|
redirectMsg_ = singleChildElementValue(app, "redirect-message", redirectMsg_);
|
||
|
setBoolean(app, "behind-reverse-proxy", behindReverseProxy_);
|
||
|
forwardedHeader_ = singleChildElementValue(app, "forwarded-header", forwardedHeader_);
|
||
|
const std::string trustedProxiesStr = singleChildElementValue(app, "trusted-proxies", "");
|
||
|
if (!trustedProxiesStr.empty()) {
|
||
|
trustedProxies_.clear();
|
||
|
std::vector<std::string> splitTrustedProxies;
|
||
|
boost::split(splitTrustedProxies, trustedProxiesStr, boost::is_any_of(","));
|
||
|
for (auto &trustedProxyStr : splitTrustedProxies) {
|
||
|
boost::trim(trustedProxyStr);
|
||
|
try {
|
||
|
trustedProxies_.push_back(Network::fromString(trustedProxyStr));
|
||
|
} catch (const std::invalid_argument &e) {
|
||
|
throw WServer::Exception("Invalid <trusted-proxies>: " + std::string(e.what()));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
setBoolean(app, "strict-event-serialization", serializedEvents_);
|
||
|
setBoolean(app, "web-sockets", webSockets_);
|
||
| src/web/Configuration.h | ||
|---|---|---|
|
#include "WebSession.h"
|
||
|
#include "Wt/WRandom.h"
|
||
|
#ifndef WT_TARGET_JAVA
|
||
|
#include "Wt/AsioWrapper/asio.hpp"
|
||
|
#endif // WT_TARGET_JAVA
|
||
|
#ifndef WT_TARGET_JAVA
|
||
|
#include "EntryPoint.h"
|
||
|
#endif // WT_TARGET_JAVA
|
||
| ... | ... | |
|
const std::string *property(const std::string& name) const;
|
||
|
#endif
|
||
|
#ifndef WT_TARGET_JAVA
|
||
|
using IpAddress = AsioWrapper::asio::ip::address;
|
||
|
#else
|
||
|
struct IpAddress {};
|
||
|
#endif
|
||
|
struct WT_API Network {
|
||
|
IpAddress address;
|
||
|
unsigned char prefixLength;
|
||
|
static Network fromString(const std::string &s);
|
||
|
bool contains(const IpAddress &address) const;
|
||
|
};
|
||
|
void setAppRoot(const std::string& path);
|
||
|
std::string appRoot() const;
|
||
|
bool behindReverseProxy() const;
|
||
|
std::string forwardedHeader() const;
|
||
|
std::vector<Network> trustedProxies() const;
|
||
|
bool isTrustedProxy(const std::string &ipAddress) const;
|
||
|
std::string redirectMessage() const;
|
||
|
bool serializedEvents() const;
|
||
|
bool webSockets() const;
|
||
| ... | ... | |
|
void setUseSlashExceptionForInternalPaths(bool enabled);
|
||
|
void setNeedReadBodyBeforeResponse(bool needed);
|
||
|
void setBehindReverseProxy(bool enabled);
|
||
|
void setForwardedHeader(const std::string &forwardedHeader);
|
||
|
void setTrustedProxies(const std::vector<Network> &trustedProxies);
|
||
|
std::string generateSessionId();
|
||
|
bool registerSessionId(const std::string& oldId, const std::string& newId);
|
||
| ... | ... | |
|
PropertyMap properties_;
|
||
|
bool xhtmlMimeType_;
|
||
|
bool behindReverseProxy_;
|
||
|
std::string forwardedHeader_;
|
||
|
std::vector<Network> trustedProxies_;
|
||
|
std::string redirectMsg_;
|
||
|
bool serializedEvents_;
|
||
|
bool webSockets_;
|
||
| src/web/WebRequest.C | ||
|---|---|---|
|
#include "WebRequest.h"
|
||
|
#include "WebUtils.h"
|
||
|
#include "Configuration.h"
|
||
|
#include <cstdlib>
|
||
| ... | ... | |
|
return s ? std::string(s) : std::string();
|
||
|
}
|
||
|
bool isPrivateIP(const std::string &s) {
|
||
|
return boost::starts_with(s, "127.") ||
|
||
|
boost::starts_with(s, "10.") ||
|
||
|
boost::starts_with(s, "192.168.") ||
|
||
|
(s.size() >= 7 &&
|
||
|
boost::starts_with(s, "172.") &&
|
||
|
s[6] == '.' &&
|
||
|
((s[4] == '1' &&
|
||
|
s[5] >= '6' &&
|
||
|
s[5] <= '9') ||
|
||
|
(s[4] == '2' &&
|
||
|
s[5] >= '0' &&
|
||
|
s[5] <= '9') ||
|
||
|
(s[4] == '3' &&
|
||
|
s[5] >= '0' &&
|
||
|
s[5] <= '1')));
|
||
|
}
|
||
|
}
|
||
|
namespace Wt {
|
||
| ... | ... | |
|
return urlParams_;
|
||
|
}
|
||
|
std::string WebRequest::clientAddress(const bool behindReverseProxy) const
|
||
|
std::string WebRequest::clientAddress(const Configuration &conf) const
|
||
|
{
|
||
|
std::string result;
|
||
|
/*
|
||
|
* Determine client address, taking into account proxies
|
||
|
*/
|
||
|
if (behindReverseProxy) {
|
||
|
std::string clientIp = str(headerValue("Client-IP"));
|
||
|
boost::trim(clientIp);
|
||
|
std::vector<std::string> ips;
|
||
|
if (!clientIp.empty())
|
||
|
boost::split(ips, clientIp, boost::is_any_of(","));
|
||
|
std::string forwardedFor = str(headerValue("X-Forwarded-For"));
|
||
|
std::string remoteAddr = str(envValue("REMOTE_ADDR"));
|
||
|
if (conf.isTrustedProxy(remoteAddr)) {
|
||
|
std::string forwardedFor = str(headerValue(conf.forwardedHeader().c_str()));
|
||
|
boost::trim(forwardedFor);
|
||
|
std::vector<std::string> forwardedIps;
|
||
|
if (!forwardedFor.empty())
|
||
|
boost::split(forwardedIps, forwardedFor, boost::is_any_of(","));
|
||
|
Utils::insert(ips, forwardedIps);
|
||
|
for (unsigned i = 0; i < ips.size(); ++i) {
|
||
|
result = ips[i];
|
||
|
boost::trim(result);
|
||
|
if (!result.empty()
|
||
|
&& !isPrivateIP(result)) {
|
||
|
break;
|
||
|
boost::split(forwardedIps, forwardedFor, boost::is_any_of(","));
|
||
|
for (auto it = forwardedIps.rbegin();
|
||
|
it != forwardedIps.rend(); ++it) {
|
||
|
boost::trim(*it);
|
||
|
if (!it->empty() && !conf.isTrustedProxy(*it)) {
|
||
|
return *it;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (result.empty())
|
||
|
result = str(envValue("REMOTE_ADDR"));
|
||
|
return result;
|
||
|
return remoteAddr;
|
||
|
}
|
||
|
}
|
||
| src/web/WebRequest.h | ||
|---|---|---|
|
namespace Wt {
|
||
|
class Configuration;
|
||
|
class EntryPoint;
|
||
|
class WSslInfo;
|
||
| ... | ... | |
|
* Returns \c nullptr if the request does not have SSL client certificate
|
||
|
* information.
|
||
|
*/
|
||
|
virtual std::unique_ptr<WSslInfo> sslInfo(bool behindReverseProxy) const = 0;
|
||
|
virtual std::unique_ptr<WSslInfo> sslInfo(const Configuration & conf) const = 0;
|
||
|
virtual const std::vector<std::pair<std::string, std::string> >& urlParams() const;
|
||
|
std::string clientAddress(bool behindReverseProxy) const;
|
||
|
std::string clientAddress(const Configuration & conf) const;
|
||
|
protected:
|
||
|
const EntryPoint *entryPoint_;
|
||
| src/web/WebSession.C | ||
|---|---|---|
|
return;
|
||
|
}
|
||
|
std::string ca = handler.request()->clientAddress(
|
||
|
controller_->configuration().behindReverseProxy());
|
||
|
std::string ca = handler.request()->clientAddress(controller_->configuration());
|
||
|
if (ca != env_->clientAddress()) {
|
||
|
bool isInvalid = sessionIdCookie_.empty();
|
||
| src/web/WebSocketMessage.C | ||
|---|---|---|
|
return "http";
|
||
|
}
|
||
|
std::unique_ptr<Wt::WSslInfo> WebSocketMessage::sslInfo(bool behindReverseProxy) const
|
||
|
std::unique_ptr<Wt::WSslInfo> WebSocketMessage::sslInfo(const Configuration & conf) const
|
||
|
{
|
||
|
return webSocket()->sslInfo(behindReverseProxy);
|
||
|
return webSocket()->sslInfo(conf);
|
||
|
}
|
||
|
const char *WebSocketMessage::headerValue(const char *name) const
|
||
| src/web/WebSocketMessage.h | ||
|---|---|---|
|
virtual const char *urlScheme() const override;
|
||
|
virtual std::unique_ptr<Wt::WSslInfo> sslInfo(bool behindReverseProxy) const override;
|
||
|
virtual std::unique_ptr<Wt::WSslInfo> sslInfo(const Configuration & conf) const override;
|
||
|
virtual const char * headerValue(const char *name) const override;
|
||
| test/http/HttpClientServerTest.C | ||
|---|---|---|
|
#include <Wt/Http/ResponseContinuation.h>
|
||
|
#include <Wt/Http/Request.h>
|
||
|
#include <web/Configuration.h>
|
||
|
#include <chrono>
|
||
|
#include <condition_variable>
|
||
|
#include <mutex>
|
||
| ... | ... | |
|
delaySendingBody_(false),
|
||
|
haveEverMoreData_(false),
|
||
|
haveRandomMoreData_(false),
|
||
|
clientAddressTest_(false),
|
||
|
aborted_(0)
|
||
|
{ }
|
||
| ... | ... | |
|
haveRandomMoreData_ = true;
|
||
|
}
|
||
|
void clientAddressTest() {
|
||
|
clientAddressTest_ = true;
|
||
|
}
|
||
|
int abortedCount() const {
|
||
|
return aborted_;
|
||
|
}
|
||
| ... | ... | |
|
{
|
||
|
if (continuation_)
|
||
|
handleWithContinuation(request, response);
|
||
|
else if (clientAddressTest_)
|
||
|
handleClientAddress(request, response);
|
||
|
else
|
||
|
handleSimple(request, response);
|
||
|
}
|
||
| ... | ... | |
|
bool delaySendingBody_;
|
||
|
bool haveEverMoreData_;
|
||
|
bool haveRandomMoreData_;
|
||
|
bool clientAddressTest_;
|
||
|
int aborted_;
|
||
|
void handleSimple(const Http::Request& request,
|
||
| ... | ... | |
|
response.out() << "Hello";
|
||
|
}
|
||
|
void handleClientAddress(const Http::Request& request,
|
||
|
Http::Response &response)
|
||
|
{
|
||
|
response.setStatus(200);
|
||
|
response.out() << request.clientAddress();
|
||
|
}
|
||
|
void handleWithContinuation(const Http::Request& request,
|
||
|
Http::Response& response)
|
||
|
{
|
||
| ... | ... | |
|
client.get("http://" + server.address() + "/test");
|
||
|
client.waitDone();
|
||
|
BOOST_REQUIRE(client.err() == Wt::AsioWrapper::asio::error::bad_descriptor);
|
||
|
BOOST_REQUIRE(client.err() == Wt::AsioWrapper::asio::error::bad_descriptor ||
|
||
|
client.err() == Wt::AsioWrapper::asio::error::operation_aborted);
|
||
|
}
|
||
|
server.resource().haveMoreData();
|
||
| ... | ... | |
|
}
|
||
|
}
|
||
|
// Reserved example IP ranges:
|
||
|
// - 192.0.2.0/24
|
||
|
// - 198.51.100.0/24
|
||
|
// - 203.0.113.0/24
|
||
|
// - 2001:db8::/32
|
||
|
BOOST_AUTO_TEST_CASE( http_client_address_not_behind_reverse_proxy )
|
||
|
{
|
||
|
Server server;
|
||
|
server.resource().clientAddressTest();
|
||
|
if (server.start()) {
|
||
|
Client client;
|
||
|
std::vector<Http::Message::Header> headers {
|
||
|
{"X-Forwarded-For", "198.51.100.1"},
|
||
|
{"Client-IP", "203.0.113.1"}
|
||
|
};
|
||
|
client.get("http://" + server.address() + "/test", headers);
|
||
|
client.waitDone();
|
||
|
BOOST_REQUIRE(!client.err());
|
||
|
BOOST_REQUIRE(client.message().status() == 200);
|
||
|
// Client-IP and X-Forwarded-For headers are ignored if not behind reverse proxy
|
||
|
BOOST_REQUIRE(client.message().body() == "127.0.0.1");
|
||
|
}
|
||
|
}
|
||
|
BOOST_AUTO_TEST_CASE( http_client_address_forwarded_for )
|
||
|
{
|
||
|
Server server;
|
||
|
server.resource().clientAddressTest();
|
||
|
server.configuration().setBehindReverseProxy(true);
|
||
|
if (server.start()) {
|
||
|
Client client;
|
||
|
std::vector<Http::Message::Header> headers {
|
||
|
{"X-Forwarded-For", "198.51.100.1"},
|
||
|
{"Client-IP", "203.0.113.1"}
|
||
|
};
|
||
|
client.get("http://" + server.address() + "/test", headers);
|
||
|
client.waitDone();
|
||
|
BOOST_REQUIRE(!client.err());
|
||
|
BOOST_REQUIRE(client.message().status() == 200);
|
||
|
// Should get IP address from X-Forwarded-For
|
||
|
BOOST_REQUIRE(client.message().body() == "198.51.100.1");
|
||
|
}
|
||
|
}
|
||
|
BOOST_AUTO_TEST_CASE( http_client_address_client_ip )
|
||
|
{
|
||
|
Server server;
|
||
|
server.resource().clientAddressTest();
|
||
|
server.configuration().setBehindReverseProxy(true);
|
||
|
server.configuration().setForwardedHeader("Client-IP");
|
||
|
if (server.start()) {
|
||
|
Client client;
|
||
|
std::vector<Http::Message::Header> headers {
|
||
|
{"X-Forwarded-For", "198.51.100.1"},
|
||
|
{"Client-IP", "203.0.113.1"}
|
||
|
};
|
||
|
client.get("http://" + server.address() + "/test", headers);
|
||
|
client.waitDone();
|
||
|
BOOST_REQUIRE(!client.err());
|
||
|
BOOST_REQUIRE(client.message().status() == 200);
|
||
|
// Should get IP address from X-Forwarded-For
|
||
|
BOOST_REQUIRE(client.message().body() == "203.0.113.1");
|
||
|
}
|
||
|
}
|
||
|
BOOST_AUTO_TEST_CASE( http_multiple_proxies )
|
||
|
{
|
||
|
Server server;
|
||
|
server.resource().clientAddressTest();
|
||
|
server.configuration().setBehindReverseProxy(true);
|
||
|
server.configuration().setTrustedProxies({
|
||
|
Configuration::Network::fromString("127.0.0.1"),
|
||
|
Configuration::Network::fromString("198.51.100.0/24")
|
||
|
});
|
||
|
if (server.start()) {
|
||
|
Client client;
|
||
|
std::vector<Http::Message::Header> headers {
|
||
|
{"X-Forwarded-For", "192.0.2.1, 203.0.113.1, 198.51.100.1"},
|
||
|
};
|
||
|
client.get("http://" + server.address() + "/test", headers);
|
||
|
client.waitDone();
|
||
|
BOOST_REQUIRE(!client.err());
|
||
|
BOOST_REQUIRE(client.message().status() == 200);
|
||
|
// Should get IP address from X-Forwarded-For
|
||
|
BOOST_REQUIRE(client.message().body() == "203.0.113.1");
|
||
|
}
|
||
|
}
|
||
|
BOOST_AUTO_TEST_CASE( http_multiple_proxies2 )
|
||
|
{
|
||
|
Server server;
|
||
|
server.resource().clientAddressTest();
|
||
|
server.configuration().setBehindReverseProxy(true);
|
||
|
server.configuration().setTrustedProxies({
|
||
|
Configuration::Network::fromString("127.0.0.1"),
|
||
|
Configuration::Network::fromString("198.51.100.0/24"),
|
||
|
Configuration::Network::fromString("203.0.113.0/24")
|
||
|
});
|
||
|
if (server.start()) {
|
||
|
Client client;
|
||
|
std::vector<Http::Message::Header> headers {
|
||
|
{"X-Forwarded-For", "192.0.2.1, 203.0.113.1, 198.51.100.1"},
|
||
|
};
|
||
|
client.get("http://" + server.address() + "/test", headers);
|
||
|
client.waitDone();
|
||
|
BOOST_REQUIRE(!client.err());
|
||
|
BOOST_REQUIRE(client.message().status() == 200);
|
||
|
// Should get IP address from X-Forwarded-For
|
||
|
BOOST_REQUIRE(client.message().body() == "192.0.2.1");
|
||
|
}
|
||
|
}
|
||
|
#endif // WT_THREADED
|
||
| test/private/EventDecodeTest.C | ||
|---|---|---|
|
return std::vector<Wt::Http::Message::Header>{};
|
||
|
}
|
||
|
virtual std::unique_ptr<Wt::WSslInfo> sslInfo(bool) const override
|
||
|
virtual std::unique_ptr<Wt::WSslInfo> sslInfo(const Wt::Configuration &) const override
|
||
|
{
|
||
|
return nullptr;
|
||
|
}
|
||
| wt_config.xml.in | ||
|---|---|---|
|
-->
|
||
|
<behind-reverse-proxy>false</behind-reverse-proxy>
|
||
|
<!-- Which header to use to get the real client IP address when behind a reverse proxy.
|
||
|
This could be X-Forwarded-For, CF-Connecting-IP for Cloudflare, True-Client-IP, Fastly-Client-IP,...
|
||
|
Defaults to X-Forwarded-For
|
||
|
-->
|
||
|
<forwarded-header>X-Forwarded-For</forwarded-header>
|
||
|
<!-- Which proxy servers are trusted when behind-reverse-proxy is true.
|
||
|
You can either enter single IP addresses or IP ranges in CIDR notation,
|
||
|
separated by commas. Whitespace is ignored.
|
||
|
By default, localhost and local IP ranges are trusted.
|
||
|
-->
|
||
|
<!--<trusted-proxies>10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fd00::/8,127.0.0.1,::1</trusted-proxies>-->
|
||
|
<!-- Whether inline CSS is allowed.
|
||
|
Some Wt widgets will insert CSS rules in the the inline
|
||