improving actor to better support unmanaged hosts

This commit is contained in:
Adolfo Gómez García 2020-05-28 04:29:13 +02:00
parent 7a9dbc3edd
commit 7ec2197634
4 changed files with 116 additions and 12 deletions

View File

@ -246,6 +246,22 @@ class UDSServerApi(UDSApi):
password=result['password']
)
def notifyUnmanagedCallback(self, master_token: str, secret: str, interfaces: typing.Iterable[types.InterfaceInfoType], port: int) -> types.CertificateInfoType:
payload = {
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces],
'token': master_token,
'secret': secret,
'port': port
}
result = self._doPost('unmanaged', payload)
return types.CertificateInfoType(
private_key=result['private_key'],
server_certificate=result['server_certificate'],
password=result['password']
)
def login(self, own_token: str, username: str) -> types.LoginResultInfoType:
if not own_token:
return types.LoginResultInfoType(

View File

@ -58,7 +58,8 @@ from .http import clients_pool, server, cert
class CommonService: # pylint: disable=too-many-instance-attributes
_isAlive: bool = True
_rebootRequested: bool = False
_loggedIn = False
_loggedIn: bool = False
_initialized: bool = False
_cfg: types.ActorConfigurationType
_api: rest.UDSServerApi
@ -229,24 +230,41 @@ class CommonService: # pylint: disable=too-many-instance-attributes
return True
def initializeUnmanaged(self) -> bool:
# Notify UDS about my callback
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)
except Exception as e:
logger.error('Couuld not notify unmanaged callback: %s', e)
return True
def getInterfaces(self) -> None:
if self._interfaces:
return
while self._isAlive:
self._interfaces = list(platform.operations.getNetworkInfo())
if self._interfaces:
break
self.doWait(5000)
def initialize(self) -> bool:
if 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
# Force time sync, just in case...
if self.isManaged():
platform.operations.forceTimeSync()
# Wait for Broker to be ready
while self._isAlive:
if not self._interfaces:
self._interfaces = list(platform.operations.getNetworkInfo())
if not self._interfaces: # Wait a bit for interfaces to get initialized... (has valid IPs)
self.doWait(5000)
continue
# Ensure we have intefaces...
self.getInterfaces()
while self._isAlive:
try:
# If master token is present, initialize and get configuration data
if self._cfg.master_token:
@ -284,6 +302,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
return self.configureMachine()
def uninitialize(self):
self._initialized = False
def finish(self) -> None:
if self._http:
self._http.stop()
@ -398,6 +419,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
self.onLogout(username)
if not self.isManaged():
self.uninitialize()
self._cfg = self._cfg._replace(own_token=None) # Ensures assigned token is cleared
# ****************************************

View File

@ -53,7 +53,8 @@ from uds.core.util.config import GlobalConfig
from ..handlers import Handler, AccessDenied, RequestError
# Not imported at runtime, just for type checking
# if typing.TYPE_CHECKING:
if typing.TYPE_CHECKING:
from uds.core import services
logger = logging.getLogger(__name__)
@ -308,7 +309,7 @@ class BaseReadyChange(ActorV3Action):
osManager.toReady(userService)
userServiceManager().notifyReadyFromOsManager(userService, '')
# Generates a certificate and send it to client. Currently, we do not store it locally
# Generates a certificate and send it to client.
privateKey, cert, password = certs.selfSignedCert(self._params['ip'])
# Store certificate with userService
userService.setProperty('cert', cert)
@ -321,7 +322,6 @@ class ChangeIp(BaseReadyChange):
"""
Processses IP Change. Needs to be "last" on a lead to be auto added to list of available methods
"""
name = 'changeip'
class Ready(BaseReadyChange):
@ -449,6 +449,55 @@ class Ticket(ActorV3Action):
except TicketStore.DoesNotExists:
return ActorV3Action.actorResult(error='Invalid ticket')
class Unmanaged(ActorV3Action):
name = 'unmanaged'
def action(self) -> typing.MutableMapping[str, typing.Any]:
"""
Changeip method expect a json POST with this fields:
* id: List[dict] -> List of dictionary containing ip and mac:
* token: str -> Valid Actor "master_token" (if invalid, will return an error).
* secret: Secret for commsUrl for actor
* port: port of the listener (normally 43910)
This method will also regenerater the public-private key pair for client, that will be needed for the new ip
Returns: {
private_key: str -> Generated private key, PEM
server_certificate: str -> Generated public key, PEM
}
"""
logger.debug('Args: %s, Params: %s', self._args, self._params)
try:
dbService: Service = Service.objects.get(token=self._params['token'])
service: 'services.Service' = dbService.getInstance()
except Exception:
return ActorV3Action.actorResult(error='Invalid token')
# Build the possible ids and ask service if it recognizes any of it
# If not recognized, will generate anyway the certificate, but will not be saved
idsList = [x['ip'] for x in self._params['id']] + [x['mac'] for x in self._params['id']][:10]
validId: typing.Optional[str] = service.getValidId(idsList)
ip: str
try:
ip = next(x['ip'] for x in self._params['id'] if x['ip'] == validId or x['mac'] == validId)
except StopIteration:
ip = self._params['id'][0]['ip'] # Get first IP if no valid ip found
# Generates a certificate and send it to client.
privateKey, cert, password = certs.selfSignedCert(ip)
cert = {'private_key': privateKey, 'server_certificate': cert, 'password': password}
# Store certificate, secret & port with service if validId
if validId:
service.storeIdInfo(validId, {
'cert': cert,
'secret': self._params['secret'],
'port': int(self._params['port'])
})
return ActorV3Action.actorResult(cert)
class Notify(ActorV3Action):
name = 'notify'
@ -476,3 +525,4 @@ class Notify(ActorV3Action):
incFailedIp(self._request.ip) # pylint: disable=protected-access
raise AccessDenied('Access denied')

View File

@ -289,7 +289,21 @@ class Service(Module):
By default, services does not have a token
"""
return None
def getValidId(self, idsList: typing.Iterable[str]) -> typing.Optional[str]:
return None
def storeIdInfo(self, id: str, data: typing.Any) -> None:
self.storage.putPickle('__nfo_' + id, data)
def recoverIdInfo(self, id: str, delete: bool = False) -> typing.Any:
# recovers the information
value = self.storage.getPickle('__nfo_' + id)
if value and delete:
self.storage.delete('__nfo_' + id)
return value
def doLog(self, level: int, message: str) -> None:
"""
Logs a message with requested level associated with this service