forked from shaba/openuds
Implementing new IPC mechanics
This commit is contained in:
parent
20bceadda9
commit
a6cb350454
0
actor/src/udsactor/http/client.py
Normal file
0
actor/src/udsactor/http/client.py
Normal file
@ -1,3 +1,33 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 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
|
||||||
|
'''
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
@ -12,5 +42,3 @@ class Handler:
|
|||||||
self._service = service
|
self._service = service
|
||||||
self._method = method
|
self._method = method
|
||||||
self._params = params
|
self._params = params
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
# Copyright (c) 2019 Virtual Cable S.L.
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# Redistribution and use in source and binary forms, with or without modification,
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
@ -36,11 +36,18 @@ if typing.TYPE_CHECKING:
|
|||||||
from ..service import CommonService
|
from ..service import CommonService
|
||||||
|
|
||||||
class LocalProvider(handler.Handler):
|
class LocalProvider(handler.Handler):
|
||||||
|
|
||||||
def post_login(self) -> typing.Any:
|
def post_login(self) -> typing.Any:
|
||||||
return 'ok'
|
result = self._service.login(self._params['username'])
|
||||||
|
return result._asdict()
|
||||||
|
|
||||||
def post_logout(self) -> typing.Any:
|
def post_logout(self) -> typing.Any:
|
||||||
|
self._service.logout(self._params['username'])
|
||||||
return 'ok'
|
return 'ok'
|
||||||
|
|
||||||
def post_ping(self) -> typing.Any:
|
def post_ping(self) -> typing.Any:
|
||||||
|
return 'pong'
|
||||||
|
|
||||||
|
def post_register(self) -> typing.Any:
|
||||||
|
self._service._registry.register(self._params['url']) # pylint: disable=protected-access
|
||||||
return 'ok'
|
return 'ok'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
# Copyright (c) 2019 Virtual Cable S.L.
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# Redistribution and use in source and binary forms, with or without modification,
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
75
actor/src/udsactor/http/registry.py
Normal file
75
actor/src/udsactor/http/registry.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 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
|
||||||
|
'''
|
||||||
|
import json
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from ..log import logger
|
||||||
|
|
||||||
|
class UDSActorClientRegistry:
|
||||||
|
_clientUrl: typing.List[str]
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._clientUrl = []
|
||||||
|
|
||||||
|
def _post(self, method: str, data: typing.Any = None) -> None:
|
||||||
|
removables: typing.List[str] = []
|
||||||
|
for clientUrl in self._clientUrl:
|
||||||
|
try:
|
||||||
|
requests.post(clientUrl + '/' + method, data=json.dumps(data), verify=False)
|
||||||
|
except Exception as e:
|
||||||
|
# If cannot request to a clientUrl, remove it from list
|
||||||
|
logger.info('Could not coneect with client %s: %s. Removed from registry.', e, clientUrl)
|
||||||
|
removables.append(clientUrl)
|
||||||
|
|
||||||
|
# Remove failed connections
|
||||||
|
for clientUrl in removables:
|
||||||
|
self.unregister(clientUrl)
|
||||||
|
|
||||||
|
def register(self, clientUrl: str) -> None:
|
||||||
|
# Remove first if exists, to avoid duplicates
|
||||||
|
self.unregister(clientUrl)
|
||||||
|
# And add it again
|
||||||
|
self._clientUrl.append(clientUrl)
|
||||||
|
|
||||||
|
def unregister(self, clientUrl: str) -> None:
|
||||||
|
self._clientUrl = list((i for i in self._clientUrl if i != clientUrl))
|
||||||
|
|
||||||
|
def executeScript(self, script: str) -> None:
|
||||||
|
self._post('script', script)
|
||||||
|
|
||||||
|
def logout(self) -> None:
|
||||||
|
self._post('logout', None)
|
||||||
|
|
||||||
|
def ping(self) -> bool:
|
||||||
|
self._post('ping', None)
|
||||||
|
return bool(self._clientUrl) # if no clients available
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
# Copyright (c) 2019 Virtual Cable S.L.
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# Redistribution and use in source and binary forms, with or without modification,
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
@ -81,6 +81,7 @@ class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
handlerType = PublicProvider
|
handlerType = PublicProvider
|
||||||
elif len(path) == 2 and path[0] == 'ui':
|
elif len(path) == 2 and path[0] == 'ui':
|
||||||
# private method, only from localhost
|
# private method, only from localhost
|
||||||
|
if self.client_address[0][:3] == '127':
|
||||||
handlerType = LocalProvider
|
handlerType = LocalProvider
|
||||||
|
|
||||||
if not handlerType:
|
if not handlerType:
|
||||||
@ -88,7 +89,7 @@ class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = getattr(handlerType(self._service, method, params), method + '_' + path[-1])()
|
result = getattr(handlerType(self._service, method, params), method + '_' + path[-1])() # last part of path is method
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.sendJsonResponse(error='Method not found', code=404)
|
self.sendJsonResponse(error='Method not found', code=404)
|
||||||
return
|
return
|
||||||
|
@ -69,7 +69,7 @@ class Logger:
|
|||||||
self.remoteLogger = remoteLogger
|
self.remoteLogger = remoteLogger
|
||||||
self.own_token = own_token
|
self.own_token = own_token
|
||||||
|
|
||||||
def log(self, level: typing.Union[str, int], message: str) -> None:
|
def log(self, level: typing.Union[str, int], message: str, *args) -> None:
|
||||||
level = int(level)
|
level = int(level)
|
||||||
if level < self.logLevel: # Skip not wanted messages
|
if level < self.logLevel: # Skip not wanted messages
|
||||||
return
|
return
|
||||||
@ -77,26 +77,26 @@ class Logger:
|
|||||||
# If remote logger is available, notify message to it
|
# If remote logger is available, notify message to it
|
||||||
try:
|
try:
|
||||||
if self.remoteLogger:
|
if self.remoteLogger:
|
||||||
self.remoteLogger.log(self.own_token, level, message)
|
self.remoteLogger.log(self.own_token, level, message % args)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.localLogger.log(FATAL, 'Error notifying log to broker: {}'.format(e))
|
self.localLogger.log(FATAL, 'Error notifying log to broker: {}'.format(e))
|
||||||
|
|
||||||
self.localLogger.log(level, message)
|
self.localLogger.log(level, message)
|
||||||
|
|
||||||
def debug(self, message: str) -> None:
|
def debug(self, message: str, *args) -> None:
|
||||||
self.log(DEBUG, message)
|
self.log(DEBUG, message, *args)
|
||||||
|
|
||||||
def warn(self, message: str) -> None:
|
def warn(self, message: str, *args) -> None:
|
||||||
self.log(WARN, message)
|
self.log(WARN, message, *args)
|
||||||
|
|
||||||
def info(self, message: str) -> None:
|
def info(self, message: str, *args) -> None:
|
||||||
self.log(INFO, message)
|
self.log(INFO, message, *args)
|
||||||
|
|
||||||
def error(self, message: str) -> None:
|
def error(self, message: str, *args) -> None:
|
||||||
self.log(ERROR, message)
|
self.log(ERROR, message, *args)
|
||||||
|
|
||||||
def fatal(self, message: str) -> None:
|
def fatal(self, message: str, *args) -> None:
|
||||||
self.log(FATAL, message)
|
self.log(FATAL, message, *args)
|
||||||
|
|
||||||
def exception(self) -> None:
|
def exception(self) -> None:
|
||||||
try:
|
try:
|
||||||
|
@ -93,23 +93,6 @@ class REST:
|
|||||||
|
|
||||||
raise RESTError(result.content)
|
raise RESTError(result.content)
|
||||||
|
|
||||||
def _login(self, auth: str, username: str, password: str) -> typing.MutableMapping[str, str]:
|
|
||||||
try:
|
|
||||||
# First, try to login
|
|
||||||
authInfo = {'auth': auth, 'username': username, 'password': password}
|
|
||||||
headers = self._headers
|
|
||||||
result = requests.post(self.url + 'auth/login', data=json.dumps(authInfo), headers=headers, verify=self.validateCert)
|
|
||||||
if not result.ok or result.json()['result'] == 'error':
|
|
||||||
raise Exception() # Invalid credentials
|
|
||||||
except requests.ConnectionError as e:
|
|
||||||
raise RESTConnectionError(str(e))
|
|
||||||
except Exception as e:
|
|
||||||
raise RESTError('Invalid credentials')
|
|
||||||
|
|
||||||
headers['X-Auth-Token'] = result.json()['token']
|
|
||||||
|
|
||||||
return headers
|
|
||||||
|
|
||||||
def enumerateAuthenticators(self) -> typing.Iterable[types.AuthenticatorType]:
|
def enumerateAuthenticators(self) -> typing.Iterable[types.AuthenticatorType]:
|
||||||
try:
|
try:
|
||||||
result = requests.get(self.url + 'auth/auths', headers=self._headers, verify=self.validateCert, timeout=4)
|
result = requests.get(self.url + 'auth/auths', headers=self._headers, verify=self.validateCert, timeout=4)
|
||||||
@ -153,8 +136,17 @@ class REST:
|
|||||||
'log_level': logLevel
|
'log_level': logLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# First, try to login to REST api
|
||||||
try:
|
try:
|
||||||
headers = self._login(auth, username, password)
|
# First, try to login
|
||||||
|
authInfo = {'auth': auth, 'username': username, 'password': password}
|
||||||
|
headers = self._headers
|
||||||
|
result = requests.post(self.url + 'auth/login', data=json.dumps(authInfo), headers=headers, verify=self.validateCert)
|
||||||
|
if not result.ok or result.json()['result'] == 'error':
|
||||||
|
raise Exception() # Invalid credentials
|
||||||
|
|
||||||
|
headers['X-Auth-Token'] = result.json()['token']
|
||||||
|
|
||||||
result = requests.post(self.url + 'actor/v2/register', data=json.dumps(data), headers=headers, verify=self.validateCert)
|
result = requests.post(self.url + 'actor/v2/register', data=json.dumps(data), headers=headers, verify=self.validateCert)
|
||||||
if result.ok:
|
if result.ok:
|
||||||
return result.json()['result']
|
return result.json()['result']
|
||||||
@ -162,8 +154,8 @@ class REST:
|
|||||||
raise RESTConnectionError(str(e))
|
raise RESTConnectionError(str(e))
|
||||||
except RESTError:
|
except RESTError:
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception as e:
|
||||||
pass
|
raise RESTError('Invalid credentials')
|
||||||
|
|
||||||
raise RESTError(result.content)
|
raise RESTError(result.content)
|
||||||
|
|
||||||
@ -207,6 +199,23 @@ class REST:
|
|||||||
}
|
}
|
||||||
self._actorPost('ipchange', payload) # Ignores result...
|
self._actorPost('ipchange', payload) # Ignores result...
|
||||||
|
|
||||||
|
def login(self, own_token: str, username: str) -> types.LoginResultInfoType:
|
||||||
|
payload = {
|
||||||
|
'token': own_token,
|
||||||
|
'username': username
|
||||||
|
}
|
||||||
|
result = self._actorPost('login', payload)
|
||||||
|
return types.LoginResultInfoType(ip=result['ip'], hostname=result['hostname'], dead_line=result['dead_line'])
|
||||||
|
|
||||||
|
|
||||||
|
def logout(self, own_token: str, username: str) -> None:
|
||||||
|
payload = {
|
||||||
|
'token': own_token,
|
||||||
|
'username': username
|
||||||
|
}
|
||||||
|
self._actorPost('logout', payload)
|
||||||
|
|
||||||
|
|
||||||
def log(self, own_token: str, level: int, message: str) -> None:
|
def log(self, own_token: str, level: int, message: str) -> None:
|
||||||
payLoad = {
|
payLoad = {
|
||||||
'token': own_token,
|
'token': own_token,
|
||||||
|
@ -41,7 +41,7 @@ from . import rest
|
|||||||
from . import types
|
from . import types
|
||||||
# from .script_thread import ScriptExecutorThread
|
# from .script_thread import ScriptExecutorThread
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
from .http import registry
|
||||||
|
|
||||||
# def setup() -> None:
|
# def setup() -> None:
|
||||||
# cfg = platform.store.readConfig()
|
# cfg = platform.store.readConfig()
|
||||||
@ -55,7 +55,6 @@ from .log import logger
|
|||||||
# else:
|
# else:
|
||||||
# logger.setLevel(20000)
|
# logger.setLevel(20000)
|
||||||
|
|
||||||
|
|
||||||
class CommonService:
|
class CommonService:
|
||||||
_isAlive: bool = True
|
_isAlive: bool = True
|
||||||
_rebootRequested: bool = False
|
_rebootRequested: bool = False
|
||||||
@ -65,6 +64,7 @@ class CommonService:
|
|||||||
_api: rest.REST
|
_api: rest.REST
|
||||||
_interfaces: typing.List[types.InterfaceInfoType]
|
_interfaces: typing.List[types.InterfaceInfoType]
|
||||||
_secret: str
|
_secret: str
|
||||||
|
_registry: registry.UDSActorClientRegistry
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def execute(cmdLine: str, section: str) -> bool:
|
def execute(cmdLine: str, section: str) -> bool:
|
||||||
@ -81,6 +81,7 @@ class CommonService:
|
|||||||
self._interfaces = []
|
self._interfaces = []
|
||||||
self._api = rest.REST(self._cfg.host, self._cfg.validateCertificate)
|
self._api = rest.REST(self._cfg.host, self._cfg.validateCertificate)
|
||||||
self._secret = secrets.token_urlsafe(33)
|
self._secret = secrets.token_urlsafe(33)
|
||||||
|
self._registry = registry.UDSActorClientRegistry()
|
||||||
|
|
||||||
# Initialzies loglevel
|
# Initialzies loglevel
|
||||||
logger.setLevel(self._cfg.log_level * 10000)
|
logger.setLevel(self._cfg.log_level * 10000)
|
||||||
@ -179,7 +180,7 @@ class CommonService:
|
|||||||
while self._isAlive:
|
while self._isAlive:
|
||||||
if not self._interfaces:
|
if not self._interfaces:
|
||||||
self._interfaces = list(platform.operations.getNetworkInfo())
|
self._interfaces = list(platform.operations.getNetworkInfo())
|
||||||
if not self._interfaces: # Wait a bit for interfaces to get initialized...
|
if not self._interfaces: # Wait a bit for interfaces to get initialized... (has valid IPs)
|
||||||
self.doWait(5000)
|
self.doWait(5000)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -281,6 +282,16 @@ class CommonService:
|
|||||||
'''
|
'''
|
||||||
logger.info('Base join invoked: {} on {}, {}'.format(name, domain, ou))
|
logger.info('Base join invoked: {} on {}, {}'.format(name, domain, ou))
|
||||||
|
|
||||||
|
# Client notifications
|
||||||
|
def login(self, username: str) -> types.LoginResultInfoType:
|
||||||
|
if self._cfg.own_token:
|
||||||
|
return self._api.login(self._cfg.own_token, username)
|
||||||
|
return types.LoginResultInfoType(ip='', hostname='', dead_line=None)
|
||||||
|
|
||||||
|
def logout(self, username: str) -> None:
|
||||||
|
if self._cfg.own_token:
|
||||||
|
self._api.logout(self._cfg.own_token, username)
|
||||||
|
|
||||||
# ****************************************
|
# ****************************************
|
||||||
# Methods that CAN BE overriden by actors
|
# Methods that CAN BE overriden by actors
|
||||||
# ****************************************
|
# ****************************************
|
||||||
|
@ -48,3 +48,8 @@ class InitializationResultType(typing.NamedTuple):
|
|||||||
unique_id: typing.Optional[str] = None
|
unique_id: typing.Optional[str] = None
|
||||||
max_idle: typing.Optional[int] = None
|
max_idle: typing.Optional[int] = None
|
||||||
os: typing.Optional[ActorOsConfigurationType] = None
|
os: typing.Optional[ActorOsConfigurationType] = None
|
||||||
|
|
||||||
|
class LoginResultInfoType(typing.NamedTuple):
|
||||||
|
ip: str
|
||||||
|
hostname: str
|
||||||
|
dead_line: typing.Optional[int]
|
||||||
|
@ -192,7 +192,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
|||||||
return 'done'
|
return 'done'
|
||||||
|
|
||||||
def onLogout(self, userName) -> None:
|
def onLogout(self, userName) -> None:
|
||||||
logger.debug('Windows onLogout invoked: {}, {}'.format(user, self._user))
|
logger.debug('Windows onLogout invoked: {}, {}'.format(userName, self._user))
|
||||||
try:
|
try:
|
||||||
p = win32security.GetBinarySid(REMOTE_USERS_SID)
|
p = win32security.GetBinarySid(REMOTE_USERS_SID)
|
||||||
groupName = win32security.LookupAccountSid(None, p)[0]
|
groupName = win32security.LookupAccountSid(None, p)[0]
|
||||||
|
Loading…
Reference in New Issue
Block a user