1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-11 05:17:55 +03:00

adapting actor to support unmanaged hosts

This commit is contained in:
Adolfo Gómez 2020-02-10 12:01:38 +01:00
parent 56376ecd56
commit efe7bb2a53
10 changed files with 243 additions and 22 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# Copyright (c) 2020 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# Copyright (c) 2020 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,

View File

@ -0,0 +1,194 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
# pylint: disable=invalid-name
import sys
import os
import logging
import typing
import PyQt5 # pylint: disable=unused-import
from PyQt5.QtWidgets import QApplication, QDialog, QFileDialog, QMessageBox
import udsactor
from ui.setup_dialog_ui import Ui_UdsActorSetupDialog
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from PyQt5.QtWidgets import QLineEdit # pylint: disable=ungrouped-imports
logger = logging.getLogger('actor')
class UDSConfigDialog(QDialog):
_host: str = ''
def __init__(self):
QDialog.__init__(self, None)
# Get local config config
config: udsactor.types.ActorConfigurationType = udsactor.platform.store.readConfig()
self.ui = Ui_UdsActorSetupDialog()
self.ui.setupUi(self)
self.ui.host.setText(config.host)
self.ui.validateCertificate.setCurrentIndex(1 if config.validateCertificate else 0)
self.ui.postConfigCommand.setText(config.post_command or '')
self.ui.preCommand.setText(config.pre_command or '')
self.ui.runonceCommand.setText(config.runonce_command or '')
self.ui.logLevelComboBox.setCurrentIndex(config.log_level)
if config.host:
self.updateAuthenticators()
self.ui.username.setText('')
self.ui.password.setText('')
self.ui.testButton.setEnabled(bool(config.master_token and config.host))
@property
def api(self) -> udsactor.rest.UDSServerApi:
return udsactor.rest.UDSServerApi(self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1)
def browse(self, lineEdit: 'QLineEdit', caption: str) -> None:
name = QFileDialog.getOpenFileName(parent=self, caption=caption, directory=os.path.dirname(lineEdit.text()))[0]
if name:
if ' ' in name:
name = '"' + name + '"'
lineEdit.setText(os.path.normpath(name))
def browsePreconnect(self) -> None:
self.browse(self.ui.preCommand, 'Select Preconnect command')
def browseRunOnce(self) -> None:
self.browse(self.ui.runonceCommand, 'Select Runonce command')
def browsePostConfig(self) -> None:
self.browse(self.ui.postConfigCommand, 'Select Postconfig command')
def updateAuthenticators(self) -> None:
if self.ui.host.text() != self._host:
self._host = self.ui.host.text()
self.ui.authenticators.clear()
auth: udsactor.types.AuthenticatorType
auths = list(self.api.enumerateAuthenticators())
if auths:
for auth in auths:
self.ui.authenticators.addItem(auth.auth, userData=auth)
# Last, add "admin" authenticator (for uds root user)
self.ui.authenticators.addItem('Administration', userData=udsactor.types.AuthenticatorType('admin', 'admin', 'admin', 'admin', 1, False))
def textChanged(self):
enableButtons = bool(self.ui.host.text() and self.ui.username.text() and self.ui.password.text() and self.ui.authenticators.currentText())
self.ui.registerButton.setEnabled(enableButtons)
self.ui.testButton.setEnabled(False) # Only registered information can be checked
def finish(self):
self.close()
def testUDSServer(self):
config: udsactor.types.ActorConfigurationType = udsactor.platform.store.readConfig()
if not config.master_token:
self.ui.testButton.setEnabled(False)
return
try:
api = udsactor.rest.UDSServerApi(config.host, config.validateCertificate)
if not api.test(config.master_token):
QMessageBox.information(
self,
'UDS Test',
'Current configured token seems to be invalid for {}. Please, request a new one.'.format(config.host),
QMessageBox.Ok
)
else:
QMessageBox.information(
self,
'UDS Test',
'Configuration for {} seems to be correct.'.format(config.host),
QMessageBox.Ok
)
except Exception:
QMessageBox.information(
self,
'UDS Test',
'Configured host {} seems to be inaccesible.'.format(config.host),
QMessageBox.Ok
)
def registerWithUDS(self):
# Get network card. Will fail if no network card is available, but don't mind (not contempled)
data: udsactor.types.InterfaceInfoType = next(udsactor.platform.operations.getNetworkInfo())
try:
token = self.api.register(
self.ui.authenticators.currentData().auth,
self.ui.username.text(),
self.ui.password.text(),
udsactor.platform.operations.getComputerName(),
data.ip or '', # IP
data.mac or '', # MAC
self.ui.preCommand.text(),
self.ui.runonceCommand.text(),
self.ui.postConfigCommand.text(),
self.ui.logLevelComboBox.currentIndex() # Loglevel
)
# Store parameters on register for later use, notify user of registration
udsactor.platform.store.writeConfig(
udsactor.types.ActorConfigurationType(
host=self.ui.host.text(),
validateCertificate=self.ui.validateCertificate.currentIndex() == 1,
master_token=token,
pre_command=self.ui.preCommand.text(),
post_command=self.ui.postConfigCommand.text(),
runonce_command=self.ui.runonceCommand.text(),
log_level=self.ui.logLevelComboBox.currentIndex()
)
)
# Enables test button
self.ui.testButton.setEnabled(True)
# Informs the user
QMessageBox.information(self, 'UDS Registration', 'Registration with UDS completed.', QMessageBox.Ok)
except udsactor.rest.RESTError as e:
self.ui.testButton.setEnabled(False)
QMessageBox.critical(self, 'UDS Registration', 'UDS Registration error: {}'.format(e), QMessageBox.Ok)
if __name__ == "__main__":
# If to be run as "sudo" on linux, we will need this to avoid problems
if 'linux' in sys.platform:
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)
sys.exit(1)
myapp = UDSConfigDialog()
myapp.show()
sys.exit(app.exec_())

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# Copyright (c) 2020 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,

View File

@ -69,13 +69,15 @@ class UDSActorSvc(daemon.Daemon, CommonService):
set_proctitle('UDSActorDaemon')
# Linux daemon will continue running unless something is requested to
if not self.initialize():
self.finish()
return # Stop daemon if initializes told to do so
# Unmanaged services does not initializes "on start", but rather when user logs in (because userservice does not exists "as such" before that)
if self.isManaged():
if not self.initialize():
self.finish()
return # Stop daemon if initializes told to do so
# logger.debug('Initialized, setting ready')
# Initialization is done, set machine to ready for UDS, communicate urls, etc...
self.setReady()
# logger.debug('Initialized, setting ready')
# Initialization is done, set machine to ready for UDS, communicate urls, etc...
self.setReady()
# *********************
# * Main Service loop *

View File

@ -51,6 +51,7 @@ def readConfig() -> types.ActorConfigurationType:
data = pickle.loads(base64.b64decode(base64Data.encode())) if base64Data else None
return types.ActorConfigurationType(
actorType=uds.get('type', types.MANAGED),
host=uds.get('host', ''),
validateCertificate=uds.getboolean('validate', fallback=False),
master_token=uds.get('master_token', None),
@ -74,6 +75,7 @@ def writeConfig(config: types.ActorConfigurationType) -> None:
def writeIfValue(val, name):
if val:
uds[name] = val
writeIfValue(config.actorType, 'type')
writeIfValue(config.master_token, 'master_token')
writeIfValue(config.own_token, 'own_token')
writeIfValue(config.pre_command, 'pre_command')

View File

@ -192,9 +192,10 @@ class UDSServerApi(UDSApi):
raise RESTError(result.content.decode())
def initialize(self, token: str, interfaces: typing.Iterable[types.InterfaceInfoType]) -> types.InitializationResultType:
def initialize(self, token: str, interfaces: typing.Iterable[types.InterfaceInfoType], actorType: typing.Optional[str]) -> types.InitializationResultType:
# Generate id list from netork cards
payload = {
'type': actorType or types.MANAGED,
'token': token,
'version': VERSION,
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces]

View File

@ -106,6 +106,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self._http = server.HTTPServerThread(self)
self._http.start()
def isManaged(self) -> bool:
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]:
"""
returns the inteface with unique_id mac or first interface or None if no interfaces...
@ -125,8 +128,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self._rebootRequested = True
def setReady(self) -> None:
if not self._isAlive:
if not self._isAlive or not self.isManaged():
return
# Unamanged actor types does not set ready never (has no osmanagers, no needing for this)
# First, if postconfig is available, execute it and disable it
if self._cfg.post_command:
@ -176,6 +180,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
if not self._isAlive:
return False
if not self.isManaged():
return True
# First, if runonce is present, honor it and remove it from config
# Return values is "True" for keep service (or daemon) running, False if Stop it.
if self._cfg.runonce_command:
@ -222,7 +229,8 @@ class CommonService: # pylint: disable=too-many-instance-attributes
return False
# Force time sync, just in case...
platform.operations.forceTimeSync()
if self.isManaged():
platform.operations.forceTimeSync()
# Wait for Broker to be ready
while self._isAlive:
@ -235,13 +243,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)
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))
return False
# Only removes token for managed machines
master_token = None if self.isManaged() else self._cfg.master_token
self._cfg = self._cfg._replace(
master_token=None,
master_token=master_token,
own_token=initResult.own_token,
config=types.ActorDataConfigurationType(
unique_id=initResult.unique_id,
@ -249,7 +259,7 @@ class CommonService: # pylint: disable=too-many-instance-attributes
)
)
# On first successfull initialization request, master token will dissapear so it will be no more available (not needed anyway)
# On first successfull initialization request, master token will dissapear for managed hosts so it will be no more available (not needed anyway)
platform.store.writeConfig(self._cfg)
# Setup logger now
if self._cfg.own_token:
@ -280,6 +290,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self.notifyStop()
def checkIpsChanged(self) -> None:
if not self.isManaged():
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:
# Not enouth data do check
@ -361,6 +374,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
def login(self, username: str) -> types.LoginResultInfoType:
result = types.LoginResultInfoType(ip='', hostname='', dead_line=None, max_idle=None)
self._loggedIn = True
if not self.isManaged():
self.initialize()
if self._cfg.own_token:
result = self._api.login(self._cfg.own_token, username)
return result

View File

@ -1,5 +1,8 @@
import typing
MANAGED = 'managed'
UNMANAGED = 'unmanaged'
class InterfaceInfoType(typing.NamedTuple):
name: str
mac: str
@ -29,6 +32,7 @@ class ActorDataConfigurationType(typing.NamedTuple):
class ActorConfigurationType(typing.NamedTuple):
host: str
validateCertificate: bool
actorType: typing.Optional[str] = None
master_token: typing.Optional[str] = None
own_token: typing.Optional[str] = None

View File

@ -218,14 +218,16 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
pythoncom.CoInitialize() # pylint: disable=no-member
if not self.initialize():
logger.info('Service stopped due to init')
self.finish()
win32event.WaitForSingleObject(self._hWaitStop, 5000)
return # Stop daemon if initializes told to do so
# Unmanaged services does not initializes "on start", but rather when user logs in (because userservice does not exists "as such" before that)
if self.isManaged():
if not self.initialize():
logger.info('Service stopped due to init')
self.finish()
win32event.WaitForSingleObject(self._hWaitStop, 5000)
return # Stop daemon if initializes told to do so
# Initialization is done, set machine to ready for UDS, communicate urls, etc...
self.setReady()
# Initialization is done, set machine to ready for UDS, communicate urls, etc...
self.setReady()
# # ********************************
# # * Registers SENS subscriptions *