diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 675c39563b..911732fcef 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -388,6 +388,23 @@ "default": "", "advanced": false }, + { + "name": "ac_subnet_whitelist", + "label": "Assignment Client IP address Whitelist", + "type": "table", + "can_add_new_rows": true, + "help": "The IP addresses or subnets of ACs that can connect to this server. You can specify an IP address or a subnet in CIDR notation ('A.B.C.D/E', Example: '10.0.0.0/24'). Local ACs (localhost) are always permitted and do not need to be added here.", + "numbered": false, + "advanced": true, + "columns": [ + { + "name": "ip", + "label": "IP Address", + "type": "ip", + "can_set": true + } + ] + }, { "name": "standard_permissions", "type": "table", diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d13f9b883f..5208cb2326 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -158,6 +158,42 @@ DomainServer::DomainServer(int argc, char* argv[]) : qDebug() << "domain-server is running"; + static const QString AC_SUBNET_WHITELIST_SETTING_PATH = "security.ac_subnet_whitelist"; + + static const Subnet LOCALHOST { QHostAddress("127.0.0.1"), 32 }; + _acSubnetWhitelist = { LOCALHOST }; + + auto whitelist = _settingsManager.valueOrDefaultValueForKeyPath(AC_SUBNET_WHITELIST_SETTING_PATH).toStringList(); + for (auto& subnet : whitelist) { + auto netmaskParts = subnet.trimmed().split("/"); + + if (netmaskParts.size() > 2) { + qDebug() << "Ignoring subnet in whitelist, malformed: " << subnet; + continue; + } + + // The default netmask is 32 if one has not been specified, which will + // match only the ip provided. + int netmask = 32; + + if (netmaskParts.size() == 2) { + bool ok; + netmask = netmaskParts[1].toInt(&ok); + if (!ok) { + qDebug() << "Ignoring subnet in whitelist, bad netmask: " << subnet; + continue; + } + } + + auto ip = QHostAddress(netmaskParts[0]); + + if (!ip.isNull()) { + qDebug() << "Adding AC whitelist subnet: " << subnet << " -> " << (ip.toString() + "/" + QString::number(netmask)); + _acSubnetWhitelist.push_back({ ip , netmask }); + } else { + qDebug() << "Ignoring subnet in whitelist, invalid ip portion: " << subnet; + } + } } void DomainServer::parseCommandLine() { @@ -1001,6 +1037,21 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointergetSenderSockAddr().getAddress(); + + auto isHostAddressInSubnet = [&senderAddr](const Subnet& mask) -> bool { + return senderAddr.isInSubnet(mask); + }; + + auto it = find_if(_acSubnetWhitelist.begin(), _acSubnetWhitelist.end(), isHostAddressInSubnet); + if (it == _acSubnetWhitelist.end()) { + static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex( + "Received an assignment connect request from a disallowed ip address: [^ ]+"); + qDebug() << "Received an assignment connect request from a disallowed ip address:" + << senderAddr.toString(); + return; + } + // Suppress these for Assignment::AgentType to once per 5 seconds static QElapsedTimer noisyMessageTimer; static bool wasNoisyTimerStarted = false; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c14ec5eee0..73135695eb 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -36,6 +36,9 @@ typedef QSharedPointer SharedAssignmentPointer; typedef QMultiHash TransactionHash; +using Subnet = QPair; +using SubnetList = std::vector; + class DomainServer : public QCoreApplication, public HTTPSRequestHandler { Q_OBJECT public: @@ -156,6 +159,8 @@ private: void setupGroupCacheRefresh(); + SubnetList _acSubnetWhitelist; + DomainGatekeeper _gatekeeper; HTTPManager _httpManager; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index c7944bbcad..f460ddd115 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1077,6 +1077,9 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson } bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { + static const QString SECURITY_ROOT_KEY = "security"; + static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; + auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; @@ -1127,7 +1130,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject); - if (rootKey != "security") { + if (rootKey != SECURITY_ROOT_KEY) { needRestart = true; } } else { @@ -1143,7 +1146,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { QJsonValue settingValue = rootValue.toObject()[settingKey]; updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject); - if (rootKey != "security") { + if (rootKey != SECURITY_ROOT_KEY || settingKey == AC_SUBNET_WHITELIST_KEY) { needRestart = true; } } else { diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index 252079f182..a1be6db448 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -226,5 +226,5 @@ QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing); } - return NULL; + return nullptr; }