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
|