From e8244e931328ab8451d60622f60e6abc0469fcd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 13 Oct 2025 13:16:16 +0200 Subject: [PATCH] Disable insecure interfaces if configured using env --- .../jsonrpc/configurationhandler.cpp | 49 ++++++++++++++----- libnymea-core/jsonrpc/tagshandler.cpp | 1 + libnymea-core/nymeaconfiguration.cpp | 4 ++ libnymea-core/nymeaconfiguration.h | 3 +- libnymea-core/servermanager.cpp | 35 +++++++++++-- libnymea-core/servermanager.h | 3 +- 6 files changed, 78 insertions(+), 17 deletions(-) diff --git a/libnymea-core/jsonrpc/configurationhandler.cpp b/libnymea-core/jsonrpc/configurationhandler.cpp index 6de2111b..2647bc5b 100644 --- a/libnymea-core/jsonrpc/configurationhandler.cpp +++ b/libnymea-core/jsonrpc/configurationhandler.cpp @@ -449,9 +449,9 @@ JsonReply *ConfigurationHandler::SetLocation(const QVariantMap ¶ms) const JsonReply *ConfigurationHandler::SetTcpServerConfiguration(const QVariantMap ¶ms) const { ServerConfiguration config = unpack(params.value("configuration").toMap()); - if (config.id.isEmpty()) { + if (config.id.isEmpty()) return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorInvalidId)); - } + if (config.address.isNull()) return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorInvalidHostAddress)); @@ -460,8 +460,17 @@ JsonReply *ConfigurationHandler::SetTcpServerConfiguration(const QVariantMap &pa return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorInvalidPort)); } - qCDebug(dcJsonRpc()) << QString("Configure TCP server %1:%2").arg(config.address).arg(config.port); + // To be compliant with the EN18031 we have to make sure the user cannot configure an insecure interface to the server. + if (qEnvironmentVariable("NYMEA_INSECURE_INTERFACES_DISABLED", "0") != "0") { + bool isLocalhost = config.address == "localhost" || config.address == "127.0.0.1"; + bool isSecured = config.sslEnabled && config.authenticationEnabled; + if (!isLocalhost && !isSecured) { + qCWarning(dcJsonRpc()) << "Cannot add insecure TCP server configuration" << config << "because insecure interfaces to the core are explicit disabled."; + return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorUnsupported)); + } + } + qCDebug(dcJsonRpc()) << QString("Configure TCP server %1:%2").arg(config.address).arg(config.port); NymeaCore::instance()->configuration()->setTcpServerConfiguration(config); return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorNoError)); } @@ -510,9 +519,9 @@ JsonReply *ConfigurationHandler::DeleteWebServerConfiguration(const QVariantMap JsonReply *ConfigurationHandler::SetWebSocketServerConfiguration(const QVariantMap ¶ms) const { ServerConfiguration config = unpack(params.value("configuration").toMap()); - if (config.id.isEmpty()) { + if (config.id.isEmpty()) return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorInvalidId)); - } + if (config.address.isNull()) return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorInvalidHostAddress)); @@ -521,6 +530,16 @@ JsonReply *ConfigurationHandler::SetWebSocketServerConfiguration(const QVariantM return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorInvalidPort)); } + // To be compliant with the EN18031 we have to make sure the user cannot configure an insecure interface to the server. + if (qEnvironmentVariable("NYMEA_INSECURE_INTERFACES_DISABLED", "0") != "0") { + bool isLocalhost = config.address == "localhost" || config.address == "127.0.0.1"; + bool isSecured = config.sslEnabled && config.authenticationEnabled; + if (!isLocalhost && !isSecured) { + qCWarning(dcJsonRpc()) << "Cannot add insecure WebSocket server configuration" << config << "because insecure interfaces to the core are explicit disabled."; + return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorUnsupported)); + } + } + qCDebug(dcJsonRpc()) << QString("Configuring web socket server %1:%2").arg(config.address).arg(config.port); NymeaCore::instance()->configuration()->setWebSocketServerConfiguration(config); @@ -541,9 +560,9 @@ JsonReply *ConfigurationHandler::DeleteWebSocketServerConfiguration(const QVaria JsonReply *ConfigurationHandler::SetTunnelProxyServerConfiguration(const QVariantMap ¶ms) const { TunnelProxyServerConfiguration config = unpack(params.value("configuration").toMap()); - if (config.id.isEmpty()) { + if (config.id.isEmpty()) return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorInvalidId)); - } + if (config.address.isNull()) return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorInvalidHostAddress)); @@ -552,6 +571,14 @@ JsonReply *ConfigurationHandler::SetTunnelProxyServerConfiguration(const QVarian return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorInvalidPort)); } + // To be compliant with the EN18031 we have to make sure the user cannot configure an insecure interface to the server. + if (qEnvironmentVariable("NYMEA_INSECURE_INTERFACES_DISABLED", "0") != "0") { + if (!config.sslEnabled || !config.authenticationEnabled || config.ignoreSslErrors) { + qCWarning(dcJsonRpc()) << "Cannot add insecure tunnelproxy server configuration" << config << "because insecure interfaces to the core are explicit disabled."; + return createReply(statusToReply(NymeaConfiguration::ConfigurationErrorUnsupported)); + } + } + qCDebug(dcJsonRpc()) << QString("Configuring tunnel proxy server %1:%2").arg(config.address).arg(config.port); NymeaCore::instance()->configuration()->setTunnelProxyServerConfiguration(config); @@ -758,10 +785,10 @@ QVariantMap ConfigurationHandler::packBasicConfiguration() basicConfiguration.insert("timeZone", QTimeZone::systemTimeZoneId()); basicConfiguration.insert("language", NymeaCore::instance()->configuration()->locale().name()); basicConfiguration.insert("location", QVariantMap{ - {"latitude", NymeaCore::instance()->configuration()->locationLatitude()}, - {"longitude", NymeaCore::instance()->configuration()->locationLongitude()}, - {"name", NymeaCore::instance()->configuration()->locationName()} - }); + {"latitude", NymeaCore::instance()->configuration()->locationLatitude()}, + {"longitude", NymeaCore::instance()->configuration()->locationLongitude()}, + {"name", NymeaCore::instance()->configuration()->locationName()} + }); basicConfiguration.insert("debugServerEnabled", NymeaCore::instance()->configuration()->debugServerEnabled()); return basicConfiguration; } diff --git a/libnymea-core/jsonrpc/tagshandler.cpp b/libnymea-core/jsonrpc/tagshandler.cpp index 45a4cefb..5ad123af 100644 --- a/libnymea-core/jsonrpc/tagshandler.cpp +++ b/libnymea-core/jsonrpc/tagshandler.cpp @@ -30,6 +30,7 @@ #include "tagshandler.h" +#include "loggingcategories.h" #include "nymeacore.h" #include "tagging/tagsstorage.h" diff --git a/libnymea-core/nymeaconfiguration.cpp b/libnymea-core/nymeaconfiguration.cpp index b1ebaac3..dbe5a80b 100644 --- a/libnymea-core/nymeaconfiguration.cpp +++ b/libnymea-core/nymeaconfiguration.cpp @@ -325,8 +325,12 @@ QLocale NymeaConfiguration::locale() const void NymeaConfiguration::setLocale(const QLocale &locale) { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) qCDebug(dcConfiguration()) << "Configuration: Set system locale:" << locale.name() << locale.nativeCountryName() << locale.nativeLanguageName(); +#else + qCDebug(dcConfiguration()) << "Configuration: Set system locale:" << locale.name() << locale.nativeTerritoryName() << locale.nativeLanguageName(); +#endif NymeaSettings settings(NymeaSettings::SettingsRoleGlobal); settings.beginGroup("nymead"); if (settings.value("language").toString() == locale.name()) { diff --git a/libnymea-core/nymeaconfiguration.h b/libnymea-core/nymeaconfiguration.h index 982252a5..e3953cfb 100644 --- a/libnymea-core/nymeaconfiguration.h +++ b/libnymea-core/nymeaconfiguration.h @@ -129,7 +129,8 @@ public: ConfigurationErrorInvalidPort, ConfigurationErrorInvalidHostAddress, ConfigurationErrorBluetoothHardwareNotAvailable, - ConfigurationErrorInvalidCertificate + ConfigurationErrorInvalidCertificate, + ConfigurationErrorUnsupported }; Q_ENUM(ConfigurationError) diff --git a/libnymea-core/servermanager.cpp b/libnymea-core/servermanager.cpp index 7eae098d..63ee3ede 100644 --- a/libnymea-core/servermanager.cpp +++ b/libnymea-core/servermanager.cpp @@ -49,6 +49,7 @@ #include "platform/platform.h" #include "platform/platformzeroconfcontroller.h" #include "version.h" +#include "loggingcategories.h" #include "jsonrpc/jsonrpcserverimplementation.h" #include "servers/mocktcpserver.h" @@ -73,6 +74,12 @@ ServerManager::ServerManager(Platform *platform, NymeaConfiguration *configurati m_nymeaConfiguration(configuration), m_sslConfiguration(QSslConfiguration()) { + // To be compliant with the EN18031 we have to make sure the user cannot configure an insecure interface to the server. + if (qEnvironmentVariable("NYMEA_INSECURE_INTERFACES_DISABLED", "0") != "0") { + qCInfo(dcServerManager()) << "Insecure interfaces to the core are explicit disabled. Not starting any unauthenticated or unencrypted interfaces."; + m_disableInsecureInterfaces = true; + } + if (!QSslSocket::supportsSsl()) { qCWarning(dcServerManager()) << "SSL is not supported/installed on this platform."; } else { @@ -102,8 +109,7 @@ ServerManager::ServerManager(Platform *platform, NymeaConfiguration *configurati } } if (certsLoaded) { - // Update this to 1.3 when minimum required Qt is 5.12 (and known client apps can deal with it) - m_sslConfiguration.setProtocol(QSsl::TlsV1_2OrLater); + m_sslConfiguration.setProtocol(QSsl::TlsV1_3OrLater); m_sslConfiguration.setPrivateKey(m_certificateKey); m_sslConfiguration.setLocalCertificate(m_certificate); } @@ -149,6 +155,11 @@ ServerManager::ServerManager(Platform *platform, NymeaConfiguration *configurati } foreach (const ServerConfiguration &config, configuration->tcpServerConfigurations()) { + if (m_disableInsecureInterfaces && (!config.sslEnabled || !config.authenticationEnabled)) { + qCWarning(dcServerManager()) << "Loaded insecure TCP server configuration" << config << "but insecure interfaces to the core are explicit disabled. This interface will not be started."; + continue; + } + TcpServer *tcpServer = new TcpServer(config, m_sslConfiguration, this); m_jsonServer->registerTransportInterface(tcpServer); m_tcpServers.insert(config.id, tcpServer); @@ -158,6 +169,11 @@ ServerManager::ServerManager(Platform *platform, NymeaConfiguration *configurati } foreach (const ServerConfiguration &config, configuration->webSocketServerConfigurations()) { + if (m_disableInsecureInterfaces && (!config.sslEnabled || !config.authenticationEnabled)) { + qCWarning(dcServerManager()) << "Loaded insecure WebSocket server configuration" << config << "but insecure interfaces to the core are explicit disabled. This interface will not be started."; + continue; + } + WebSocketServer *webSocketServer = new WebSocketServer(config, m_sslConfiguration, this); m_jsonServer->registerTransportInterface(webSocketServer); m_webSocketServers.insert(config.id, webSocketServer); @@ -170,10 +186,19 @@ ServerManager::ServerManager(Platform *platform, NymeaConfiguration *configurati m_bluetoothServer = new BluetoothServer(this); m_jsonServer->registerTransportInterface(m_bluetoothServer); if (configuration->bluetoothServerEnabled()) { - m_bluetoothServer->startServer(); + if (m_disableInsecureInterfaces) { + qCWarning(dcServerManager()) << "The bluetooth RFCOM server is enabled, but insecure server interfaces have been disabled explicitly. Not starting the bluetooth server."; + } else { + m_bluetoothServer->startServer(); + } } foreach (const TunnelProxyServerConfiguration &config, configuration->tunnelProxyServerConfigurations()) { + if (m_disableInsecureInterfaces && (!config.sslEnabled || !config.authenticationEnabled || config.ignoreSslErrors)) { + qCWarning(dcServerManager()) << "Loaded insecure tunnelproxy server configuration" << config << "but insecure interfaces to the core are explicit disabled. This interface will not be started."; + continue; + } + TunnelProxyServer *tunnelProxyServer = new TunnelProxyServer(configuration->serverName(), configuration->serverUuid(), config, this); qCDebug(dcServerManager()) << "Creating tunnel proxy server using" << config; m_tunnelProxyServers.insert(config.id, tunnelProxyServer); @@ -209,14 +234,18 @@ ServerManager::ServerManager(Platform *platform, NymeaConfiguration *configurati connect(configuration, &NymeaConfiguration::tcpServerConfigurationChanged, this, &ServerManager::tcpServerConfigurationChanged); connect(configuration, &NymeaConfiguration::tcpServerConfigurationRemoved, this, &ServerManager::tcpServerConfigurationRemoved); + connect(configuration, &NymeaConfiguration::webSocketServerConfigurationChanged, this, &ServerManager::webSocketServerConfigurationChanged); connect(configuration, &NymeaConfiguration::webSocketServerConfigurationRemoved, this, &ServerManager::webSocketServerConfigurationRemoved); + connect(configuration, &NymeaConfiguration::webServerConfigurationChanged, this, &ServerManager::webServerConfigurationChanged); connect(configuration, &NymeaConfiguration::webServerConfigurationRemoved, this, &ServerManager::webServerConfigurationRemoved); + connect(configuration, &NymeaConfiguration::mqttServerConfigurationChanged, this, &ServerManager::mqttServerConfigurationChanged); connect(configuration, &NymeaConfiguration::mqttServerConfigurationRemoved, this, &ServerManager::mqttServerConfigurationRemoved); connect(configuration, &NymeaConfiguration::mqttPolicyChanged, this, &ServerManager::mqttPolicyChanged); connect(configuration, &NymeaConfiguration::mqttPolicyRemoved, this, &ServerManager::mqttPolicyRemoved); + connect(configuration, &NymeaConfiguration::tunnelProxyServerConfigurationChanged, this, &ServerManager::tunnelProxyServerConfigurationChanged); connect(configuration, &NymeaConfiguration::tunnelProxyServerConfigurationRemoved, this, &ServerManager::tunnelProxyServerConfigurationRemoved); } diff --git a/libnymea-core/servermanager.h b/libnymea-core/servermanager.h index 3c3f937c..749a930a 100644 --- a/libnymea-core/servermanager.h +++ b/libnymea-core/servermanager.h @@ -33,7 +33,6 @@ #include -#include "loggingcategories.h" #include "nymeaconfiguration.h" #include @@ -72,7 +71,6 @@ public: public slots: void setServerName(const QString &serverName); - private slots: void tcpServerConfigurationChanged(const QString &id); void tcpServerConfigurationRemoved(const QString &id); @@ -95,6 +93,7 @@ private: private: Platform *m_platform = nullptr; + bool m_disableInsecureInterfaces = false; NymeaConfiguration *m_nymeaConfiguration = nullptr; // Interfaces