Browse Source

Merge pull request #17684 from Chocobo1/subnet

Allow to use subnet notation in reverse proxy list
adaptive-webui-19844
Chocobo1 2 years ago committed by GitHub
parent
commit
257914b0d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      src/base/preferences.cpp
  2. 17
      src/base/utils/net.cpp
  3. 10
      src/base/utils/net.h
  4. 9
      src/gui/ipsubnetwhitelistoptionsdialog.cpp
  5. 2
      src/gui/optionsdialog.ui
  6. 31
      src/webui/webapplication.cpp
  7. 4
      src/webui/webapplication.h
  8. 8
      src/webui/www/private/views/preferences.html

11
src/base/preferences.cpp

@ -548,10 +548,9 @@ QVector<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const
for (const QString &rawSubnet : subnets) for (const QString &rawSubnet : subnets)
{ {
bool ok = false; const std::optional<Utils::Net::Subnet> subnet = Utils::Net::parseSubnet(rawSubnet.trimmed());
const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(rawSubnet.trimmed(), &ok); if (subnet)
if (ok) ret.append(subnet.value());
ret.append(subnet);
} }
return ret; return ret;
@ -561,9 +560,7 @@ void Preferences::setWebUiAuthSubnetWhitelist(QStringList subnets)
{ {
Algorithm::removeIf(subnets, [](const QString &subnet) Algorithm::removeIf(subnets, [](const QString &subnet)
{ {
bool ok = false; return !Utils::Net::parseSubnet(subnet.trimmed()).has_value();
Utils::Net::parseSubnet(subnet.trimmed(), &ok);
return !ok;
}); });
setValue(u"Preferences/WebUI/AuthSubnetWhitelist"_qs, subnets); setValue(u"Preferences/WebUI/AuthSubnetWhitelist"_qs, subnets);

17
src/base/utils/net.cpp

@ -47,22 +47,15 @@ namespace Utils
return !QHostAddress(ip).isNull(); return !QHostAddress(ip).isNull();
} }
Subnet parseSubnet(const QString &subnetStr, bool *ok) std::optional<Subnet> parseSubnet(const QString &subnetStr)
{ {
const Subnet invalid = qMakePair(QHostAddress(), -1);
const Subnet subnet = QHostAddress::parseSubnet(subnetStr); const Subnet subnet = QHostAddress::parseSubnet(subnetStr);
if (ok) const Subnet invalid = {QHostAddress(), -1};
*ok = (subnet != invalid); if (subnet == invalid)
return std::nullopt;
return subnet; return subnet;
} }
bool canParseSubnet(const QString &subnetStr)
{
bool ok = false;
parseSubnet(subnetStr, &ok);
return ok;
}
bool isLoopbackAddress(const QHostAddress &addr) bool isLoopbackAddress(const QHostAddress &addr)
{ {
return (addr == QHostAddress::LocalHost) return (addr == QHostAddress::LocalHost)
@ -70,7 +63,7 @@ namespace Utils
|| (addr == QHostAddress(u"::ffff:127.0.0.1"_qs)); || (addr == QHostAddress(u"::ffff:127.0.0.1"_qs));
} }
bool isIPInRange(const QHostAddress &addr, const QVector<Subnet> &subnets) bool isIPInSubnets(const QHostAddress &addr, const QVector<Subnet> &subnets)
{ {
QHostAddress protocolEquivalentAddress; QHostAddress protocolEquivalentAddress;
bool addrConversionOk = false; bool addrConversionOk = false;

10
src/base/utils/net.h

@ -28,8 +28,10 @@
#pragma once #pragma once
#include <QHostAddress> #include <optional>
#include <QtContainerFwd> #include <QtContainerFwd>
#include <QHostAddress>
class QSslCertificate; class QSslCertificate;
class QSslKey; class QSslKey;
@ -37,13 +39,13 @@ class QString;
namespace Utils::Net namespace Utils::Net
{ {
// alias for `QHostAddress::parseSubnet()` return type
using Subnet = QPair<QHostAddress, int>; using Subnet = QPair<QHostAddress, int>;
bool isValidIP(const QString &ip); bool isValidIP(const QString &ip);
Subnet parseSubnet(const QString &subnetStr, bool *ok = nullptr); std::optional<Subnet> parseSubnet(const QString &subnetStr);
bool canParseSubnet(const QString &subnetStr);
bool isLoopbackAddress(const QHostAddress &addr); bool isLoopbackAddress(const QHostAddress &addr);
bool isIPInRange(const QHostAddress &addr, const QVector<Subnet> &subnets); bool isIPInSubnets(const QHostAddress &addr, const QVector<Subnet> &subnets);
QString subnetToString(const Subnet &subnet); QString subnetToString(const Subnet &subnet);
QHostAddress canonicalIPv6Addr(const QHostAddress &addr); QHostAddress canonicalIPv6Addr(const QHostAddress &addr);

9
src/gui/ipsubnetwhitelistoptionsdialog.cpp

@ -90,16 +90,15 @@ void IPSubnetWhitelistOptionsDialog::on_buttonBox_accepted()
void IPSubnetWhitelistOptionsDialog::on_buttonWhitelistIPSubnet_clicked() void IPSubnetWhitelistOptionsDialog::on_buttonWhitelistIPSubnet_clicked()
{ {
bool ok = false; const std::optional<Utils::Net::Subnet> subnet = Utils::Net::parseSubnet(m_ui->txtIPSubnet->text());
const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(m_ui->txtIPSubnet->text(), &ok); if (!subnet)
if (!ok)
{ {
QMessageBox::critical(this, tr("Error"), tr("The entered subnet is invalid.")); QMessageBox::critical(this, tr("Error"), tr("The entered subnet is invalid."));
return; return;
} }
m_model->insertRow(m_model->rowCount()); m_model->insertRow(m_model->rowCount());
m_model->setData(m_model->index(m_model->rowCount() - 1, 0), Utils::Net::subnetToString(subnet)); m_model->setData(m_model->index(m_model->rowCount() - 1, 0), Utils::Net::subnetToString(subnet.value()));
m_ui->txtIPSubnet->clear(); m_ui->txtIPSubnet->clear();
m_modified = true; m_modified = true;
} }
@ -114,5 +113,5 @@ void IPSubnetWhitelistOptionsDialog::on_buttonDeleteIPSubnet_clicked()
void IPSubnetWhitelistOptionsDialog::on_txtIPSubnet_textChanged(const QString &subnetStr) void IPSubnetWhitelistOptionsDialog::on_txtIPSubnet_textChanged(const QString &subnetStr)
{ {
m_ui->buttonWhitelistIPSubnet->setEnabled(Utils::Net::canParseSubnet(subnetStr)); m_ui->buttonWhitelistIPSubnet->setEnabled(Utils::Net::parseSubnet(subnetStr).has_value());
} }

2
src/gui/optionsdialog.ui

@ -3397,7 +3397,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
<item> <item>
<widget class="QLineEdit" name="textTrustedReverseProxiesList"> <widget class="QLineEdit" name="textTrustedReverseProxiesList">
<property name="toolTip"> <property name="toolTip">
<string>Specify reverse proxy IPs in order to use forwarded client address (X-Forwarded-For attribute), use ';' to split multiple entries.</string> <string>Specify reverse proxy IPs (or subnets, e.g. 0.0.0.0/24) in order to use forwarded client address (X-Forwarded-For header). Use ';' to split multiple entries.</string>
</property> </property>
</widget> </widget>
</item> </item>

31
src/webui/webapplication.cpp

@ -42,7 +42,6 @@
#include <QUrl> #include <QUrl>
#include "base/algorithm.h" #include "base/algorithm.h"
#include "base/global.h"
#include "base/http/httperror.h" #include "base/http/httperror.h"
#include "base/logger.h" #include "base/logger.h"
#include "base/preferences.h" #include "base/preferences.h"
@ -402,15 +401,29 @@ void WebApplication::configure()
m_isReverseProxySupportEnabled = pref->isWebUIReverseProxySupportEnabled(); m_isReverseProxySupportEnabled = pref->isWebUIReverseProxySupportEnabled();
if (m_isReverseProxySupportEnabled) if (m_isReverseProxySupportEnabled)
{ {
m_trustedReverseProxyList.clear();
const QStringList proxyList = pref->getWebUITrustedReverseProxiesList().split(u';', Qt::SkipEmptyParts); const QStringList proxyList = pref->getWebUITrustedReverseProxiesList().split(u';', Qt::SkipEmptyParts);
for (const QString &proxy : proxyList) m_trustedReverseProxyList.clear();
m_trustedReverseProxyList.reserve(proxyList.size());
for (QString proxy : proxyList)
{
if (!proxy.contains(u'/'))
{
const QAbstractSocket::NetworkLayerProtocol protocol = QHostAddress(proxy).protocol();
if (protocol == QAbstractSocket::IPv4Protocol)
{ {
QHostAddress ip; proxy.append(u"/32");
if (ip.setAddress(proxy)) }
m_trustedReverseProxyList.push_back(ip); else if (protocol == QAbstractSocket::IPv6Protocol)
{
proxy.append(u"/128");
}
}
const std::optional<Utils::Net::Subnet> subnet = Utils::Net::parseSubnet(proxy);
if (subnet)
m_trustedReverseProxyList.push_back(subnet.value());
} }
if (m_trustedReverseProxyList.isEmpty()) if (m_trustedReverseProxyList.isEmpty())
@ -579,7 +592,7 @@ bool WebApplication::isAuthNeeded()
{ {
if (!m_isLocalAuthEnabled && Utils::Net::isLoopbackAddress(m_clientAddress)) if (!m_isLocalAuthEnabled && Utils::Net::isLoopbackAddress(m_clientAddress))
return false; return false;
if (m_isAuthSubnetWhitelistEnabled && Utils::Net::isIPInRange(m_clientAddress, m_authSubnetWhitelist)) if (m_isAuthSubnetWhitelistEnabled && Utils::Net::isIPInSubnets(m_clientAddress, m_authSubnetWhitelist))
return false; return false;
return true; return true;
} }
@ -728,7 +741,7 @@ QHostAddress WebApplication::resolveClientAddress() const
return m_env.clientAddress; return m_env.clientAddress;
// Only reverse proxy can overwrite client address // Only reverse proxy can overwrite client address
if (!m_trustedReverseProxyList.contains(m_env.clientAddress)) if (!Utils::Net::isIPInSubnets(m_env.clientAddress, m_trustedReverseProxyList))
return m_env.clientAddress; return m_env.clientAddress;
const QString forwardedFor = m_request.headers.value(Http::HEADER_X_FORWARDED_FOR); const QString forwardedFor = m_request.headers.value(Http::HEADER_X_FORWARDED_FOR);

4
src/webui/webapplication.h

@ -34,11 +34,13 @@
#include <QDateTime> #include <QDateTime>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QHash> #include <QHash>
#include <QHostAddress>
#include <QMap> #include <QMap>
#include <QObject> #include <QObject>
#include <QRegularExpression> #include <QRegularExpression>
#include <QSet> #include <QSet>
#include <QTranslator> #include <QTranslator>
#include <QVector>
#include "base/applicationcomponent.h" #include "base/applicationcomponent.h"
#include "base/global.h" #include "base/global.h"
@ -233,7 +235,7 @@ private:
// Reverse proxy // Reverse proxy
bool m_isReverseProxySupportEnabled; bool m_isReverseProxySupportEnabled;
QVector<QHostAddress> m_trustedReverseProxyList; QVector<Utils::Net::Subnet> m_trustedReverseProxyList;
QHostAddress m_clientAddress; QHostAddress m_clientAddress;
QVector<Http::Header> m_prebuiltHeaders; QVector<Http::Header> m_prebuiltHeaders;

8
src/webui/www/private/views/preferences.html

@ -847,7 +847,11 @@
<label for="webui_domain_textarea">QBT_TR(Server domains:)QBT_TR[CONTEXT=OptionsDialog]</label> <label for="webui_domain_textarea">QBT_TR(Server domains:)QBT_TR[CONTEXT=OptionsDialog]</label>
</td> </td>
<td> <td>
<textarea id="webui_domain_textarea" rows="1" cols="60"></textarea> <textarea id="webui_domain_textarea" rows="1" cols="60" title="QBT_TR(Whitelist for filtering HTTP Host header values.
In order to defend against DNS rebinding attack,
you should put in domain names used by WebUI server.
Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsDialog]"></textarea>
</td> </td>
</tr> </tr>
</table> </table>
@ -869,7 +873,7 @@
</legend> </legend>
<div class="formRow"> <div class="formRow">
<label for="webUIReverseProxiesListTextarea">QBT_TR(Trusted proxies list:)QBT_TR[CONTEXT=OptionsDialog]</label> <label for="webUIReverseProxiesListTextarea">QBT_TR(Trusted proxies list:)QBT_TR[CONTEXT=OptionsDialog]</label>
<input type="text" id="webUIReverseProxiesListTextarea" /> <input type="text" id="webUIReverseProxiesListTextarea" title="QBT_TR(Specify reverse proxy IPs (or subnets, e.g. 0.0.0.0/24) in order to use forwarded client address (X-Forwarded-For header). Use ';' to split multiple entries.)QBT_TR[CONTEXT=OptionsDialog]" />
</div> </div>
</fieldset> </fieldset>

Loading…
Cancel
Save