diff --git a/actor/src/actor_config_unmanaged.py b/actor/src/actor_config_unmanaged.py index d11abcfa..a9dfd45f 100755 --- a/actor/src/actor_config_unmanaged.py +++ b/actor/src/actor_config_unmanaged.py @@ -49,6 +49,7 @@ if typing.TYPE_CHECKING: logger = logging.getLogger('actor') + class UDSConfigDialog(QDialog): _host: str = '' _config: udsactor.types.ActorConfigurationType @@ -60,65 +61,98 @@ class UDSConfigDialog(QDialog): self.ui = Ui_UdsActorSetupDialog() self.ui.setupUi(self) self.ui.host.setText(self._config.host) - self.ui.validateCertificate.setCurrentIndex(1 if self._config.validateCertificate else 0) + self.ui.validateCertificate.setCurrentIndex( + 1 if self._config.validateCertificate else 0 + ) self.ui.logLevelComboBox.setCurrentIndex(self._config.log_level) self.ui.serviceToken.setText(self._config.master_token or '') + self.ui.restrictNet.setText(self._config.restrict_net or '') - self.ui.testButton.setEnabled(bool(self._config.master_token and self._config.host)) + self.ui.testButton.setEnabled( + bool(self._config.master_token and self._config.host) + ) @property def api(self) -> udsactor.rest.UDSServerApi: - return udsactor.rest.UDSServerApi(self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1) + return udsactor.rest.UDSServerApi( + self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1 + ) def finish(self) -> None: self.close() def configChanged(self, text: str) -> None: - self.ui.testButton.setEnabled(self.ui.host.text() == self._config.host and self.ui.serviceToken.text() == self._config.master_token) + self.ui.testButton.setEnabled( + self.ui.host.text() == self._config.host + and self.ui.serviceToken.text() == self._config.master_token + and self._config.restrict_net == self.ui.restrictNet.text() + ) def testUDSServer(self) -> None: if not self._config.master_token or not self._config.host: self.ui.testButton.setEnabled(False) return try: - api = udsactor.rest.UDSServerApi(self._config.host, self._config.validateCertificate) + api = udsactor.rest.UDSServerApi( + self._config.host, self._config.validateCertificate + ) if not api.test(self._config.master_token, udsactor.types.UNMANAGED): QMessageBox.information( self, 'UDS Test', 'Service token seems to be invalid . Please, check token validity.', - QMessageBox.Ok + QMessageBox.Ok, ) else: QMessageBox.information( self, 'UDS Test', - 'Configuration for {} seems to be correct.'.format(self._config.host), - QMessageBox.Ok + 'Configuration for {} seems to be correct.'.format( + self._config.host + ), + QMessageBox.Ok, ) except Exception: QMessageBox.information( self, 'UDS Test', 'Configured host {} seems to be inaccesible.'.format(self._config.host), - QMessageBox.Ok + QMessageBox.Ok, ) def saveConfig(self) -> None: + # Ensure restrict_net is empty or a valid subnet + restrictNet = self.ui.restrictNet.text().strip() + try: + subnet = udsactor.tools.strToNoIPV4Network(restrictNet) + if not subnet: + raise Exception('Invalid subnet') + except Exception: + QMessageBox.information( + self, + 'Invalid subnet', + 'Invalid subnet {}. Please, check it.'.format(restrictNet), + QMessageBox.Ok, + ) + return + # Store parameters on register for later use, notify user of registration self._config = udsactor.types.ActorConfigurationType( actorType=udsactor.types.UNMANAGED, host=self.ui.host.text(), validateCertificate=self.ui.validateCertificate.currentIndex() == 1, - master_token=self.ui.serviceToken.text(), - log_level=self.ui.logLevelComboBox.currentIndex() + master_token=self.ui.serviceToken.text().strip(), + restrict_net=restrictNet, + log_level=self.ui.logLevelComboBox.currentIndex(), ) udsactor.platform.store.writeConfig(self._config) # Enables test button self.ui.testButton.setEnabled(True) # Informs the user - QMessageBox.information(self, 'UDS Configuration', 'Configuration saved.', QMessageBox.Ok) + QMessageBox.information( + self, 'UDS Configuration', 'Configuration saved.', QMessageBox.Ok + ) if __name__ == "__main__": @@ -127,9 +161,9 @@ if __name__ == "__main__": os.environ['QT_X11_NO_MITSHM'] = '1' app = QApplication(sys.argv) - + if udsactor.platform.operations.checkPermissions() is False: - QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok) + QMessageBox.critical(None, 'UDS Actor', 'This Program must be executed as administrator', QMessageBox.Ok) # type: ignore sys.exit(1) if len(sys.argv) > 2: diff --git a/actor/src/designer/setup-dialog-unmanaged.ui b/actor/src/designer/setup-dialog-unmanaged.ui index 26a71969..2c6bd953 100644 --- a/actor/src/designer/setup-dialog-unmanaged.ui +++ b/actor/src/designer/setup-dialog-unmanaged.ui @@ -10,8 +10,8 @@ 0 0 - 595 - 220 + 591 + 243 @@ -55,7 +55,7 @@ 10 - 180 + 210 181 23 @@ -83,7 +83,7 @@ 410 - 180 + 210 171 23 @@ -117,7 +117,7 @@ 210 - 180 + 210 181 23 @@ -144,7 +144,7 @@ 10 10 571 - 161 + 228 @@ -221,14 +221,14 @@ - + Log Level - + 1 @@ -258,6 +258,23 @@ + + + + Restrict Net + + + + + + + UDS user with administration rights (Will not be stored on template) + + + <html><head/><body><p>Administrator user on UDS Server.</p><p>Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.</p></body></html> + + + label_host host @@ -267,6 +284,8 @@ label_security label_loglevel logLevelComboBox + label_restrictNet + restrictNet @@ -353,6 +372,22 @@ + + restrictNet + textChanged(QString) + UdsActorSetupDialog + configChanged() + + + 341 + 139 + + + 295 + 121 + + + finish() diff --git a/actor/src/udsactor/__init__.py b/actor/src/udsactor/__init__.py index 630f9f4d..2fa9dded 100644 --- a/actor/src/udsactor/__init__.py +++ b/actor/src/udsactor/__init__.py @@ -31,6 +31,7 @@ from . import types from . import rest from . import platform +from . import tools __title__ = 'udsactor' __author__ = 'Adolfo Gómez ' diff --git a/actor/src/udsactor/linux/store.py b/actor/src/udsactor/linux/store.py index 9226ac91..869475e3 100644 --- a/actor/src/udsactor/linux/store.py +++ b/actor/src/udsactor/linux/store.py @@ -56,6 +56,7 @@ def readConfig() -> types.ActorConfigurationType: validateCertificate=uds.getboolean('validate', fallback=False), master_token=uds.get('master_token', None), own_token=uds.get('own_token', None), + restrict_net=uds.get('restrict_net', None), pre_command=uds.get('pre_command', None), runonce_command=uds.get('runonce_command', None), post_command=uds.get('post_command', None), @@ -78,6 +79,7 @@ def writeConfig(config: types.ActorConfigurationType) -> None: writeIfValue(config.actorType, 'type') writeIfValue(config.master_token, 'master_token') writeIfValue(config.own_token, 'own_token') + writeIfValue(config.restrict_net, 'restrict_net') writeIfValue(config.pre_command, 'pre_command') writeIfValue(config.post_command, 'post_command') writeIfValue(config.runonce_command, 'runonce_command') diff --git a/actor/src/udsactor/service.py b/actor/src/udsactor/service.py index 64916f5c..2347e2c1 100644 --- a/actor/src/udsactor/service.py +++ b/actor/src/udsactor/service.py @@ -39,6 +39,7 @@ import typing from . import platform from . import rest from . import types +from . import tools from .log import logger, DEBUG, INFO, ERROR, FATAL from .http import clients_pool, server, cert @@ -245,7 +246,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes return while self._isAlive: - self._interfaces = list(platform.operations.getNetworkInfo()) + self._interfaces = tools.validNetworkCards(self._cfg.restrict_net, platform.operations.getNetworkInfo()) if self._interfaces: break self.doWait(5000) diff --git a/actor/src/udsactor/tools.py b/actor/src/udsactor/tools.py index 8d87646f..38764157 100644 --- a/actor/src/udsactor/tools.py +++ b/actor/src/udsactor/tools.py @@ -28,11 +28,17 @@ ''' @author: Adolfo Gómez, dkmaster at dkmon dot com ''' -# pylint: disable=invalid-name +from re import I import threading +import ipaddress +import typing from udsactor.log import logger +if typing.TYPE_CHECKING: + from udsactor.types import ActorConfigurationType, InterfaceInfoType + + class ScriptExecutorThread(threading.Thread): def __init__(self, script: str) -> None: super(ScriptExecutorThread, self).__init__() @@ -45,3 +51,36 @@ class ScriptExecutorThread(threading.Thread): except Exception as e: logger.error('Error executing script: {}'.format(e)) logger.exception() + + +# Convert "X.X.X.X/X" to ipaddress.IPv4Network +def strToNoIPV4Network(net: typing.Optional[str]) -> typing.Optional[ipaddress.IPv4Network]: + if not net: # Empty or None + return None + try: + return ipaddress.IPv4Interface(net).network + except Exception: + return None + + +def validNetworkCards( + net: typing.Optional[str], cards: typing.Iterable[InterfaceInfoType] +) -> typing.List[InterfaceInfoType]: + try: + subnet = strToNoIPV4Network(net) + except Exception as e: + logger.error('Invalid network: {}'.format(e)) + subnet = None + + if subnet is None: + return list(cards) + + def isValid(ip: str, subnet: ipaddress.IPv4Network) -> bool: + if not ip: + return False + try: + return ipaddress.IPv4Address(ip) in subnet + except Exception: + return False + + return [c for c in cards if isValid(c.ip, subnet)] diff --git a/actor/src/udsactor/types.py b/actor/src/udsactor/types.py index 3140dd66..8e56122d 100644 --- a/actor/src/udsactor/types.py +++ b/actor/src/udsactor/types.py @@ -35,6 +35,7 @@ class ActorConfigurationType(typing.NamedTuple): actorType: typing.Optional[str] = None master_token: typing.Optional[str] = None own_token: typing.Optional[str] = None + restrict_net: typing.Optional[str] = None pre_command: typing.Optional[str] = None runonce_command: typing.Optional[str] = None diff --git a/actor/src/ui/setup_dialog_ui.py b/actor/src/ui/setup_dialog_ui.py index 38b7a480..7f6d1896 100644 --- a/actor/src/ui/setup_dialog_ui.py +++ b/actor/src/ui/setup_dialog_ui.py @@ -2,9 +2,10 @@ # Form implementation generated from reading ui file 'setup-dialog.ui' # -# Created by: PyQt5 UI code generator 5.13.2 +# Created by: PyQt5 UI code generator 5.15.2 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets diff --git a/actor/src/ui/setup_dialog_unmanaged_ui.py b/actor/src/ui/setup_dialog_unmanaged_ui.py index 7f63525e..5ed834fd 100644 --- a/actor/src/ui/setup_dialog_unmanaged_ui.py +++ b/actor/src/ui/setup_dialog_unmanaged_ui.py @@ -2,9 +2,10 @@ # Form implementation generated from reading ui file 'setup-dialog-unmanaged.ui' # -# Created by: PyQt5 UI code generator 5.13.2 +# Created by: PyQt5 UI code generator 5.15.2 # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets @@ -14,7 +15,7 @@ class Ui_UdsActorSetupDialog(object): def setupUi(self, UdsActorSetupDialog): UdsActorSetupDialog.setObjectName("UdsActorSetupDialog") UdsActorSetupDialog.setWindowModality(QtCore.Qt.WindowModal) - UdsActorSetupDialog.resize(595, 220) + UdsActorSetupDialog.resize(591, 243) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -34,12 +35,12 @@ class Ui_UdsActorSetupDialog(object): UdsActorSetupDialog.setModal(True) self.saveButton = QtWidgets.QPushButton(UdsActorSetupDialog) self.saveButton.setEnabled(True) - self.saveButton.setGeometry(QtCore.QRect(10, 180, 181, 23)) + self.saveButton.setGeometry(QtCore.QRect(10, 210, 181, 23)) self.saveButton.setMinimumSize(QtCore.QSize(181, 0)) self.saveButton.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) self.saveButton.setObjectName("saveButton") self.closeButton = QtWidgets.QPushButton(UdsActorSetupDialog) - self.closeButton.setGeometry(QtCore.QRect(410, 180, 171, 23)) + self.closeButton.setGeometry(QtCore.QRect(410, 210, 171, 23)) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -49,11 +50,11 @@ class Ui_UdsActorSetupDialog(object): self.closeButton.setObjectName("closeButton") self.testButton = QtWidgets.QPushButton(UdsActorSetupDialog) self.testButton.setEnabled(False) - self.testButton.setGeometry(QtCore.QRect(210, 180, 181, 23)) + self.testButton.setGeometry(QtCore.QRect(210, 210, 181, 23)) self.testButton.setMinimumSize(QtCore.QSize(181, 0)) self.testButton.setObjectName("testButton") self.layoutWidget = QtWidgets.QWidget(UdsActorSetupDialog) - self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 571, 161)) + self.layoutWidget.setGeometry(QtCore.QRect(10, 10, 571, 228)) self.layoutWidget.setObjectName("layoutWidget") self.formLayout = QtWidgets.QFormLayout(self.layoutWidget) self.formLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) @@ -84,7 +85,7 @@ class Ui_UdsActorSetupDialog(object): self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.serviceToken) self.label_loglevel = QtWidgets.QLabel(self.layoutWidget) self.label_loglevel.setObjectName("label_loglevel") - self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_loglevel) + self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_loglevel) self.logLevelComboBox = QtWidgets.QComboBox(self.layoutWidget) self.logLevelComboBox.setFrame(True) self.logLevelComboBox.setObjectName("logLevelComboBox") @@ -96,7 +97,13 @@ class Ui_UdsActorSetupDialog(object): self.logLevelComboBox.setItemText(2, "ERROR") self.logLevelComboBox.addItem("") self.logLevelComboBox.setItemText(3, "FATAL") - self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox) + self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.logLevelComboBox) + self.label_restrictNet = QtWidgets.QLabel(self.layoutWidget) + self.label_restrictNet.setObjectName("label_restrictNet") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_restrictNet) + self.restrictNet = QtWidgets.QLineEdit(self.layoutWidget) + self.restrictNet.setObjectName("restrictNet") + self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.restrictNet) self.label_host.raise_() self.host.raise_() self.label_serviceToken.raise_() @@ -105,6 +112,8 @@ class Ui_UdsActorSetupDialog(object): self.label_security.raise_() self.label_loglevel.raise_() self.logLevelComboBox.raise_() + self.label_restrictNet.raise_() + self.restrictNet.raise_() self.retranslateUi(UdsActorSetupDialog) self.logLevelComboBox.setCurrentIndex(1) @@ -113,6 +122,7 @@ class Ui_UdsActorSetupDialog(object): self.saveButton.clicked.connect(UdsActorSetupDialog.saveConfig) self.host.textChanged['QString'].connect(UdsActorSetupDialog.configChanged) self.serviceToken.textChanged['QString'].connect(UdsActorSetupDialog.configChanged) + self.restrictNet.textChanged['QString'].connect(UdsActorSetupDialog.configChanged) QtCore.QMetaObject.connectSlotsByName(UdsActorSetupDialog) def retranslateUi(self, UdsActorSetupDialog): @@ -139,4 +149,7 @@ class Ui_UdsActorSetupDialog(object): self.serviceToken.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)")) self.serviceToken.setWhatsThis(_translate("UdsActorSetupDialog", "

Administrator user on UDS Server.

Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.

")) self.label_loglevel.setText(_translate("UdsActorSetupDialog", "Log Level")) + self.label_restrictNet.setText(_translate("UdsActorSetupDialog", "Restrict Net")) + self.restrictNet.setToolTip(_translate("UdsActorSetupDialog", "UDS user with administration rights (Will not be stored on template)")) + self.restrictNet.setWhatsThis(_translate("UdsActorSetupDialog", "

Administrator user on UDS Server.

Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.

")) from ui import uds_rc diff --git a/actor/src/ui/uds_rc.py b/actor/src/ui/uds_rc.py index 26d1f8e7..e9a5d15e 100644 --- a/actor/src/ui/uds_rc.py +++ b/actor/src/ui/uds_rc.py @@ -2,7 +2,7 @@ # Resource object code # -# Created by: The Resource Compiler for PyQt5 (Qt v5.13.2) +# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2) # # WARNING! All changes made in this file will be lost! diff --git a/client-py3/full/src/uds/tools.py b/client-py3/full/src/uds/tools.py index d82baf66..b335e6da 100644 --- a/client-py3/full/src/uds/tools.py +++ b/client-py3/full/src/uds/tools.py @@ -234,8 +234,8 @@ def verifySignature(script: bytes, signature: bytes) -> bool: ) try: - public_key.verify( - base64.b64decode(signature), script, padding.PKCS1v15(), hashes.SHA256() + public_key.verify( # type: ignore + base64.b64decode(signature), script, padding.PKCS1v15(), hashes.SHA256() # type: ignore ) except Exception: # InvalidSignature return False