Merge remote-tracking branch 'origin/v3.5'

This commit is contained in:
Adolfo Gómez García 2022-06-21 14:28:30 +02:00
commit 2eff59908a
16 changed files with 333 additions and 107 deletions

View File

@ -67,7 +67,7 @@ if __name__ == "__main__":
# Note: Signals are only checked on python code execution, so we create a timer to force call back to python
timer = QTimer(qApp)
timer.start(1000)
timer.timeout.connect(lambda *a: None) # timeout can be connected to a callable
timer.timeout.connect(lambda *a: None) # type: ignore # timeout can be connected to a callable
qApp.exec()

View File

@ -40,6 +40,7 @@ import PyQt5 # pylint: disable=unused-import
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
import udsactor
import udsactor.tools
from ui.setup_dialog_unmanaged_ui import Ui_UdsActorSetupDialog
@ -49,6 +50,7 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger('actor')
class UDSConfigDialog(QDialog):
_host: str = ''
_config: udsactor.types.ActorConfigurationType
@ -60,65 +62,99 @@ 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.ui.restrictNet.text() == self._config.restrict_net
)
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()
if restrictNet:
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 +163,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:

View File

@ -10,8 +10,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>595</width>
<height>220</height>
<width>601</width>
<height>243</height>
</rect>
</property>
<property name="sizePolicy">
@ -55,7 +55,7 @@
<property name="geometry">
<rect>
<x>10</x>
<y>180</y>
<y>210</y>
<width>181</width>
<height>23</height>
</rect>
@ -83,7 +83,7 @@
<property name="geometry">
<rect>
<x>410</x>
<y>180</y>
<y>210</y>
<width>171</width>
<height>23</height>
</rect>
@ -117,7 +117,7 @@
<property name="geometry">
<rect>
<x>210</x>
<y>180</y>
<y>210</y>
<width>181</width>
<height>23</height>
</rect>
@ -144,7 +144,7 @@
<x>10</x>
<y>10</y>
<width>571</width>
<height>161</height>
<height>191</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
@ -221,14 +221,14 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_loglevel">
<property name="text">
<string>Log Level</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<widget class="QComboBox" name="logLevelComboBox">
<property name="currentIndex">
<number>1</number>
@ -258,6 +258,23 @@
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_restrictNet">
<property name="text">
<string>Restrict Net</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="restrictNet">
<property name="toolTip">
<string>UDS user with administration rights (Will not be stored on template)</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Administrator user on UDS Server.&lt;/p&gt;&lt;p&gt;Note: This credential will not be stored on client. Will be used to obtain an unique token for this image.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
<zorder>label_host</zorder>
<zorder>host</zorder>
@ -267,6 +284,8 @@
<zorder>label_security</zorder>
<zorder>label_loglevel</zorder>
<zorder>logLevelComboBox</zorder>
<zorder>label_restrictNet</zorder>
<zorder>restrictNet</zorder>
</widget>
</widget>
<resources>
@ -353,6 +372,22 @@
</hint>
</hints>
</connection>
<connection>
<sender>restrictNet</sender>
<signal>textChanged(QString)</signal>
<receiver>UdsActorSetupDialog</receiver>
<slot>configChanged()</slot>
<hints>
<hint type="sourcelabel">
<x>341</x>
<y>139</y>
</hint>
<hint type="destinationlabel">
<x>295</x>
<y>121</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>finish()</slot>

View File

@ -38,6 +38,7 @@ from ..log import logger
if typing.TYPE_CHECKING:
from ..service import CommonService
class PublicProvider(handler.Handler):
def post_logout(self) -> typing.Any:
logger.debug('Sending LOGOFF to clients')
@ -51,7 +52,9 @@ class PublicProvider(handler.Handler):
logger.debug('Sending MESSAGE to clients')
if 'message' not in self._params:
raise Exception('Invalid message parameters')
self._service._clientsPool.message(self._params['message']) # pylint: disable=protected-access
self._service._clientsPool.message(
self._params['message']
) # pylint: disable=protected-access
return 'ok'
def post_script(self) -> typing.Any:
@ -60,7 +63,9 @@ class PublicProvider(handler.Handler):
raise Exception('Invalid script parameters')
if self._params.get('user', False):
logger.debug('Sending SCRIPT to client')
self._service._clientsPool.executeScript(self._params['script']) # pylint: disable=protected-access
self._service._clientsPool.executeScript(
self._params['script']
) # pylint: disable=protected-access
else:
# Execute script at server space, that is, here
# as a parallel thread
@ -72,14 +77,22 @@ class PublicProvider(handler.Handler):
logger.debug('Received Pre connection')
if 'user' not in self._params or 'protocol' not in self._params:
raise Exception('Invalid preConnect parameters')
return self._service.preConnect(self._params['user'], self._params['protocol'], self._params.get('ip', 'unknown'), self._params.get('hostname', 'unknown'))
return self._service.preConnect(
self._params['user'],
self._params['protocol'],
self._params.get('ip', 'unknown'),
self._params.get('hostname', 'unknown'),
self._params.get('udsuser', 'unknown'),
)
def get_information(self) -> typing.Any:
# Return something useful? :)
return 'UDS Actor Secure Server'
def get_screenshot(self) -> typing.Any:
return self._service._clientsPool.screenshot() # pylint: disable=protected-access
return (
self._service._clientsPool.screenshot()
) # pylint: disable=protected-access
def get_uuid(self) -> typing.Any:
if self._service.isManaged():

View File

@ -159,7 +159,7 @@ class HTTPServerThread(threading.Thread):
# self._server.socket = ssl.wrap_socket(self._server.socket, certfile=self.certFile, server_side=True)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.options = ssl.CERT_NONE
# context.options = ssl.CERT_NONE
context.load_cert_chain(certfile=self._certFile, password=password)
self._server.socket = context.wrap_socket(self._server.socket, server_side=True)

View File

@ -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')

View File

@ -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
@ -55,6 +56,7 @@ from .http import clients_pool, server, cert
# else:
# logger.setLevel(20000)
class CommonService: # pylint: disable=too-many-instance-attributes
_isAlive: bool = True
_rebootRequested: bool = False
@ -75,7 +77,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
logger.debug('Executing command on {}: {}'.format(section, cmdLine))
res = subprocess.check_call(cmdLine, shell=True)
except Exception as e:
logger.error('Got exception executing: {} - {} - {}'.format(section, cmdLine, e))
logger.error(
'Got exception executing: {} - {} - {}'.format(section, cmdLine, e)
)
return False
logger.debug('Result of executing cmd for {} was {}'.format(section, res))
return True
@ -86,7 +90,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self._api = rest.UDSServerApi(self._cfg.host, self._cfg.validateCertificate)
self._secret = secrets.token_urlsafe(33)
self._clientsPool = clients_pool.UDSActorClientPool()
self._certificate = cert.defaultCertificate # For being used on "unmanaged" hosts only
self._certificate = (
cert.defaultCertificate
) # For being used on "unmanaged" hosts only
self._http = None
# Initialzies loglevel and serviceLogger
@ -112,16 +118,24 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self._http.start()
def isManaged(self) -> bool:
return self._cfg.actorType != types.UNMANAGED # Only "unmanaged" hosts are unmanaged, the rest are "managed"
return (
self._cfg.actorType != types.UNMANAGED
) # Only "unmanaged" hosts are unmanaged, the rest are "managed"
def serviceInterfaceInfo(self, interfaces: typing.Optional[typing.List[types.InterfaceInfoType]] = None) -> typing.Optional[types.InterfaceInfoType]:
def serviceInterfaceInfo(
self, interfaces: typing.Optional[typing.List[types.InterfaceInfoType]] = None
) -> typing.Optional[types.InterfaceInfoType]:
"""
returns the inteface with unique_id mac or first interface or None if no interfaces...
"""
interfaces = interfaces or self._interfaces # Emty interfaces is like "no ip change" because cannot be notified
interfaces = (
interfaces or self._interfaces
) # Emty interfaces is like "no ip change" because cannot be notified
if self._cfg.config and interfaces:
try:
return next(x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id)
return next(
x for x in interfaces if x.mac.lower() == self._cfg.config.unique_id
)
except StopIteration:
return interfaces[0]
@ -152,7 +166,12 @@ class CommonService: # pylint: disable=too-many-instance-attributes
while self._isAlive:
counter -= 1
try:
self._certificate = self._api.ready(self._cfg.own_token, self._secret, srvInterface.ip, rest.LISTEN_PORT)
self._certificate = self._api.ready(
self._cfg.own_token,
self._secret,
srvInterface.ip,
rest.LISTEN_PORT,
)
except rest.RESTConnectionError as e:
if not logged: # Only log connection problems ONCE
logged = True
@ -168,7 +187,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
# Success or any error that is not recoverable (retunerd by UDS). if Error, service will be cleaned in a while.
break
else:
logger.error('Could not locate IP address!!!. (Not registered with UDS)')
logger.error(
'Could not locate IP address!!!. (Not registered with UDS)'
)
# Do not continue if not alive...
if not self._isAlive:
@ -176,7 +197,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
# Cleans sensible data
if self._cfg.config:
self._cfg = self._cfg._replace(config=self._cfg.config._replace(os=None), data=None)
self._cfg = self._cfg._replace(
config=self._cfg.config._replace(os=None), data=None
)
platform.store.writeConfig(self._cfg)
logger.info('Service ready')
@ -195,10 +218,10 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self._cfg = self._cfg._replace(runonce_command=None)
platform.store.writeConfig(self._cfg)
if self.execute(runOnce, "runOnce"):
# If runonce is present, will not do anythin more
# So we have to ensure that, when runonce command is finished, reboots the machine.
# That is, the COMMAND itself has to restart the machine!
return False # If the command fails, continue with the rest of the operations...
# If runonce is present, will not do anythin more
# So we have to ensure that, when runonce command is finished, reboots the machine.
# That is, the COMMAND itself has to restart the machine!
return False # If the command fails, continue with the rest of the operations...
# Retry configuration while not stop service, config in case of error 10 times, reboot vm
counter = 10
@ -208,9 +231,20 @@ class CommonService: # pylint: disable=too-many-instance-attributes
if self._cfg.config and self._cfg.config.os:
osData = self._cfg.config.os
if osData.action == 'rename':
self.rename(osData.name, osData.username, osData.password, osData.new_password)
self.rename(
osData.name,
osData.username,
osData.password,
osData.new_password,
)
elif osData.action == 'rename_ad':
self.joinDomain(osData.name, osData.ad or '', osData.ou or '', osData.username or '', osData.password or '')
self.joinDomain(
osData.name,
osData.ad or '',
osData.ou or '',
osData.username or '',
osData.password or '',
)
if self._rebootRequested:
try:
@ -234,7 +268,12 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self.getInterfaces() # Ensure we have interfaces
if self._cfg.master_token:
try:
self._certificate = self._api.notifyUnmanagedCallback(self._cfg.master_token, self._secret, self._interfaces, rest.LISTEN_PORT)
self._certificate = self._api.notifyUnmanagedCallback(
self._cfg.master_token,
self._secret,
self._interfaces,
rest.LISTEN_PORT,
)
except Exception as e:
logger.error('Couuld not notify unmanaged callback: %s', e)
@ -245,13 +284,17 @@ 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)
def initialize(self) -> bool:
if self._initialized or not self._cfg.host or not self._isAlive: # Not configured or not running
if (
self._initialized or not self._cfg.host or not self._isAlive
): # Not configured or not running
return False
self._initialized = True
@ -268,9 +311,15 @@ class CommonService: # pylint: disable=too-many-instance-attributes
try:
# If master token is present, initialize and get configuration data
if self._cfg.master_token:
initResult: types.InitializationResultType = self._api.initialize(self._cfg.master_token, self._interfaces, self._cfg.actorType)
initResult: types.InitializationResultType = self._api.initialize(
self._cfg.master_token, self._interfaces, self._cfg.actorType
)
if not initResult.own_token: # Not managed
logger.debug('This host is not managed by UDS Broker (ids: {})'.format(self._interfaces))
logger.debug(
'This host is not managed by UDS Broker (ids: {})'.format(
self._interfaces
)
)
return False
# Only removes master token for managed machines (will need it on next client execution)
@ -280,9 +329,8 @@ class CommonService: # pylint: disable=too-many-instance-attributes
master_token=master_token,
own_token=initResult.own_token,
config=types.ActorDataConfigurationType(
unique_id=initResult.unique_id,
os=initResult.os
)
unique_id=initResult.unique_id, os=initResult.os
),
)
# On first successfull initialization request, master token will dissapear for managed hosts
@ -296,10 +344,16 @@ class CommonService: # pylint: disable=too-many-instance-attributes
break # Initial configuration done..
except rest.RESTConnectionError as e:
logger.info('Trying to inititialize connection with broker (last error: {})'.format(e))
logger.info(
'Trying to inititialize connection with broker (last error: {})'.format(
e
)
)
self.doWait(5000) # Wait a bit and retry
except rest.RESTError as e: # Invalid key?
logger.error('Error validating with broker. (Invalid token?): {}'.format(e))
except rest.RESTError as e: # Invalid key?
logger.error(
'Error validating with broker. (Invalid token?): {}'.format(e)
)
return False
except Exception:
logger.exception()
@ -309,7 +363,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
def uninitialize(self):
self._initialized = False
self._cfg = self._cfg._replace(own_token=None) # Ensures assigned token is cleared
self._cfg = self._cfg._replace(
own_token=None
) # Ensures assigned token is cleared
def finish(self) -> None:
if self._http:
@ -324,7 +380,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self._cfg.own_token,
'',
self._interfaces,
self._secret
self._secret,
)
except Exception as e:
logger.error('Error notifying final logout to UDS: %s', e)
@ -336,19 +392,33 @@ class CommonService: # pylint: disable=too-many-instance-attributes
return # Unamanaged hosts does not changes ips. (The full initialize-login-logout process is done in a row, so at login the IP is correct)
try:
if not self._cfg.own_token or not self._cfg.config or not self._cfg.config.unique_id:
if (
not self._cfg.own_token
or not self._cfg.config
or not self._cfg.config.unique_id
):
# Not enouth data do check
return
currentInterfaces = list(platform.operations.getNetworkInfo())
currentInterfaces = tools.validNetworkCards(
self._cfg.restrict_net, platform.operations.getNetworkInfo()
)
old = self.serviceInterfaceInfo()
new = self.serviceInterfaceInfo(currentInterfaces)
if not new or not old:
raise Exception('No ip currently available for {}'.format(self._cfg.config.unique_id))
raise Exception(
'No ip currently available for {}'.format(
self._cfg.config.unique_id
)
)
if old.ip != new.ip:
self._certificate = self._api.notifyIpChange(self._cfg.own_token, self._secret, new.ip, rest.LISTEN_PORT)
self._certificate = self._api.notifyIpChange(
self._cfg.own_token, self._secret, new.ip, rest.LISTEN_PORT
)
# Now store new addresses & interfaces...
self._interfaces = currentInterfaces
logger.info('Ip changed from {} to {}. Notified to UDS'.format(old.ip, new.ip))
logger.info(
'Ip changed from {} to {}. Notified to UDS'.format(old.ip, new.ip)
)
# Stop the running HTTP Thread and start a new one, with new generated cert
self.startHttpServer()
except Exception as e:
@ -356,12 +426,12 @@ class CommonService: # pylint: disable=too-many-instance-attributes
logger.warn('Checking ips failed: {}'.format(e))
def rename(
self,
name: str,
userName: typing.Optional[str] = None,
oldPassword: typing.Optional[str] = None,
newPassword: typing.Optional[str] = None
) -> None:
self,
name: str,
userName: typing.Optional[str] = None,
oldPassword: typing.Optional[str] = None,
newPassword: typing.Optional[str] = None,
) -> None:
'''
Invoked when broker requests a rename action
default does nothing
@ -372,10 +442,14 @@ class CommonService: # pylint: disable=too-many-instance-attributes
if userName and newPassword:
logger.info('Setting password for configured user')
try:
platform.operations.changeUserPassword(userName, oldPassword or '', newPassword)
platform.operations.changeUserPassword(
userName, oldPassword or '', newPassword
)
except Exception as e:
# Logs error, but continue renaming computer
logger.error('Could not change password for user {}: {}'.format(userName, e))
logger.error(
'Could not change password for user {}: {}'.format(userName, e)
)
if hostName.lower() == name.lower():
logger.info('Computer name is already {}'.format(hostName))
@ -400,13 +474,8 @@ class CommonService: # pylint: disable=too-many-instance-attributes
# Methods that can be overriden by linux & windows Actor
# ******************************************************
def joinDomain( # pylint: disable=unused-argument, too-many-arguments
self,
name: str,
domain: str,
ou: str,
account: str,
password: str
) -> None:
self, name: str, domain: str, ou: str, account: str, password: str
) -> None:
'''
Invoked when broker requests a "domain" action
default does nothing
@ -414,8 +483,12 @@ class CommonService: # pylint: disable=too-many-instance-attributes
logger.debug('Base join invoked: {} on {}, {}'.format(name, domain, ou))
# Client notifications
def login(self, username: str, sessionType: typing.Optional[str] = None) -> types.LoginResultInfoType:
result = types.LoginResultInfoType(ip='', hostname='', dead_line=None, max_idle=None)
def login(
self, username: str, sessionType: typing.Optional[str] = None
) -> types.LoginResultInfoType:
result = types.LoginResultInfoType(
ip='', hostname='', dead_line=None, max_idle=None
)
self._loggedIn = True
master_token = None
@ -426,7 +499,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self.initialize()
master_token = self._cfg.master_token
secret = self._secret
# Own token will not be set if UDS did not assigned the initialized VM to an user
# In that case, take master token (if machine is Unamanaged version)
token = self._cfg.own_token or master_token
@ -437,7 +510,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
username,
sessionType or '',
self._interfaces,
secret
secret,
)
script = platform.store.invokeScriptOnLogin()
@ -457,11 +530,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
token = self._cfg.own_token or master_token
if token:
self._api.logout(
self._cfg.actorType,
token,
username,
self._interfaces,
self._secret
self._cfg.actorType, token, username, self._interfaces, self._secret
)
self.onLogout(username)
@ -490,13 +559,25 @@ class CommonService: # pylint: disable=too-many-instance-attributes
'''
logger.info('Service stopped')
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str) -> str: # pylint: disable=unused-argument
def preConnect(
self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str
) -> str:
'''
Invoked when received a PRE Connection request via REST
Base preconnect executes the preconnect command
'''
if self._cfg.pre_command:
self.execute(self._cfg.pre_command + ' {} {} {} {}'.format(userName.replace('"', '%22'), protocol, ip, hostname), 'preConnect')
self.execute(
self._cfg.pre_command
+ ' {} {} {} {} {}'.format(
userName.replace('"', '%22'),
protocol,
ip,
hostname,
udsUserName.replace('"', '%22'),
),
'preConnect',
)
return 'ok'

View File

@ -28,20 +28,58 @@
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
# pylint: disable=invalid-name
import threading
import ipaddress
import typing
if typing.TYPE_CHECKING:
from udsactor.types import InterfaceInfoType
from udsactor.log import logger
class ScriptExecutorThread(threading.Thread):
def __init__(self, script: str) -> None:
super(ScriptExecutorThread, self).__init__()
self.script = script
def run(self) -> None:
from udsactor.log import logger
try:
logger.debug('Executing script: {}'.format(self.script))
exec(self.script, globals(), None) # pylint: disable=exec-used
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:
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)]

View File

@ -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

View File

@ -35,7 +35,6 @@ import tempfile
import typing
import servicemanager
from udsactor import service # pylint: disable=import-error
# Valid logging levels, from UDS Broker (uds.core.utils.log).
from .. import loglevel

View File

@ -139,7 +139,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
logger.info('Using multiple step join because configuration requests to do so')
self.multiStepJoin(name, domain, ou, account, password)
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str) -> str:
def preConnect(self, userName: str, protocol: str, ip: str, hostname: str, udsUserName: str) -> str:
logger.debug('Pre connect invoked')
if protocol == 'rdp': # If connection is not using rdp, skip adding user
@ -168,7 +168,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
self._user = None
logger.debug('User {} already in group'.format(userName))
return super().preConnect(userName, protocol, ip, hostname)
return super().preConnect(userName, protocol, ip, hostname, udsUserName)
def ovLogon(self, username: str, password: str) -> str:
"""

View File

@ -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

View File

@ -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(601, 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, 191))
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", "<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>"))
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", "<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>"))
from ui import uds_rc

View File

@ -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!

View File

@ -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

View File

@ -136,11 +136,18 @@ def notifyPreconnect(userService: 'UserService', userName: str, protocol: str) -
Notifies a preconnect to an user service
"""
ip, hostname = userService.getConnectionSource()
try:
_requestActor(
userService,
'preConnect',
{'user': userName, 'protocol': protocol, 'ip': ip, 'hostname': hostname},
{
'user': userName,
'protocol': protocol,
'ip': ip,
'hostname': hostname,
'udsuser': userService.user.name + '@' + userService.user.manager.name if userService.user else '',
},
)
except NoActorComms:
pass # If no preconnect, warning will appear on UDS log