diff --git a/actor/src/udsactor/linux/operations.py b/actor/src/udsactor/linux/operations.py index 4f360d8f..c39d2b08 100644 --- a/actor/src/udsactor/linux/operations.py +++ b/actor/src/udsactor/linux/operations.py @@ -91,12 +91,12 @@ def _getInterfaces() -> typing.List[str]: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) names = array.array(str('B'), b'\0' * space) - outbytes = struct.unpack(str('iL'), fcntl.ioctl( + outbytes = struct.unpack('iL', fcntl.ioctl( s.fileno(), 0x8912, # SIOCGIFCONF - struct.pack(str('iL'), space, names.buffer_info()[0]) + struct.pack('iL', space, names.buffer_info()[0]) ))[0] - namestr = names.tostring() + namestr = names.tobytes() # return namestr, outbytes return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)] @@ -155,7 +155,7 @@ def renameComputer(newName: str) -> bool: Returns True if reboot needed ''' rename(newName) - return True # Always reboot right now. Not much slower but much more better + return True # Always reboot right now. Not much slower but much more convenient def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False): diff --git a/server/src/uds/REST/methods/actor_v3.py b/server/src/uds/REST/methods/actor_v3.py index 026784cb..9d54c81e 100644 --- a/server/src/uds/REST/methods/actor_v3.py +++ b/server/src/uds/REST/methods/actor_v3.py @@ -41,7 +41,7 @@ from uds.models import ( TicketStore, ) -#from uds.core import VERSION +# from uds.core import VERSION from uds.core.managers import userServiceManager from uds.core import osmanagers from uds.core.util import log, certs @@ -64,6 +64,7 @@ UNMANAGED = 'unmanaged' # matches the definition of UDS Actors OFC class BlockAccess(Exception): pass + # Helpers @@ -73,7 +74,11 @@ def checkBlockedIp(ip: str) -> None: cache = Cache('actorv3') fails = cache.get(ip) or 0 if fails > ALLOWED_FAILS: - logger.info('Access to actor from %s is blocked for %s seconds since last fail', ip, GlobalConfig.LOGIN_BLOCK.getInt()) + logger.info( + 'Access to actor from %s is blocked for %s seconds since last fail', + ip, + GlobalConfig.LOGIN_BLOCK.getInt(), + ) raise BlockAccess() @@ -88,7 +93,9 @@ class ActorV3Action(Handler): path = 'actor/v3' @staticmethod - def actorResult(result: typing.Any = None, error: typing.Optional[str] = None) -> typing.MutableMapping[str, typing.Any]: + def actorResult( + result: typing.Any = None, error: typing.Optional[str] = None + ) -> typing.MutableMapping[str, typing.Any]: result = result or '' res = {'result': result, 'stamp': getSqlDatetimeAsUnix()} if error: @@ -130,6 +137,7 @@ class Test(ActorV3Action): """ Tests UDS Broker actor connectivity & key """ + name = 'test' def post(self) -> typing.MutableMapping[str, typing.Any]: @@ -138,7 +146,9 @@ class Test(ActorV3Action): if self._params.get('type') == UNMANAGED: Service.objects.get(token=self._params['token']) else: - ActorToken.objects.get(token=self._params['token']) # Not assigned, because only needs check + ActorToken.objects.get( + token=self._params['token'] + ) # Not assigned, because only needs check except Exception: return ActorV3Action.actorResult('invalid token') @@ -149,6 +159,7 @@ class Register(ActorV3Action): """ Registers an actor """ + authenticated = True needs_staff = True @@ -182,16 +193,17 @@ class Register(ActorV3Action): runonce_command=self._params['run_once_command'], log_level=self._params['log_level'], token=secrets.token_urlsafe(36), - stamp=getSqlDatetime() + stamp=getSqlDatetime(), ) return ActorV3Action.actorResult(actorToken.token) -class Initiialize(ActorV3Action): +class Initialize(ActorV3Action): """ Information about machine action. Also returns the id used for the rest of the actions. (Only this one will use actor key) """ + name = 'initialize' def action(self) -> typing.MutableMapping[str, typing.Any]: @@ -235,11 +247,15 @@ class Initiialize(ActorV3Action): service: Service = Service.objects.get(token=self._params['token']) # Locate an userService that belongs to this service and which # Build the possible ids and make initial filter to match service - idsList = [x['ip'] for x in self._params['id']] + [x['mac'] for x in self._params['id']][:10] + idsList = [x['ip'] for x in self._params['id']] + [ + x['mac'] for x in self._params['id'] + ][:10] dbFilter = UserService.objects.filter(deployed_service__service=service) else: # If not service provided token, use actor tokens - ActorToken.objects.get(token=self._params['token']) # Not assigned, because only needs check + ActorToken.objects.get( + token=self._params['token'] + ) # Not assigned, because only needs check # Build the possible ids and make initial filter to match ANY userservice with provided MAC idsList = [i['mac'] for i in self._params['id'][:5]] dbFilter = UserService.objects.all() @@ -247,19 +263,18 @@ class Initiialize(ActorV3Action): # Valid actor token, now validate access allowed. That is, look for a valid mac from the ones provided. try: userService: UserService = next( - iter(dbFilter.filter( - unique_id__in=idsList, - state__in=[State.USABLE, State.PREPARING] - )) + iter( + dbFilter.filter( + unique_id__in=idsList, + state__in=[State.USABLE, State.PREPARING], + ) + ) ) except Exception as e: logger.info('Unmanaged host request: %s, %s', self._params, e) - return ActorV3Action.actorResult({ - 'own_token': None, - 'max_idle': None, - 'unique_id': None, - 'os': None - }) + return ActorV3Action.actorResult( + {'own_token': None, 'max_idle': None, 'unique_id': None, 'os': None} + ) # Managed by UDS, get initialization data from osmanager and return it # Set last seen actor version @@ -269,11 +284,13 @@ class Initiialize(ActorV3Action): if osManager: osData = osManager.actorData(userService) - return ActorV3Action.actorResult({ - 'own_token': userService.uuid, - 'unique_id': userService.unique_id, - 'os': osData - }) + return ActorV3Action.actorResult( + { + 'own_token': userService.uuid, + 'unique_id': userService.unique_id, + 'os': osData, + } + ) except (ActorToken.DoesNotExist, Service.DoesNotExist): raise BlockAccess() @@ -282,6 +299,7 @@ class BaseReadyChange(ActorV3Action): """ Records the IP change of actor """ + name = 'notused' # Not really important, this is not a "leaf" class and will not be directly available def action(self) -> typing.MutableMapping[str, typing.Any]: @@ -309,7 +327,12 @@ class BaseReadyChange(ActorV3Action): userService.updateData(userServiceInstance) # Store communications url also - ActorV3Action.setCommsUrl(userService, self._params['ip'], int(self._params['port']), self._params['secret']) + ActorV3Action.setCommsUrl( + userService, + self._params['ip'], + int(self._params['port']), + self._params['secret'], + ) if userService.os_state != State.USABLE: userService.setOsState(State.USABLE) @@ -327,13 +350,20 @@ class BaseReadyChange(ActorV3Action): userService.setProperty('priv', privateKey) userService.setProperty('priv_passwd', password) - return ActorV3Action.actorResult({'private_key': privateKey, 'server_certificate': cert, 'password': password}) + return ActorV3Action.actorResult( + { + 'private_key': privateKey, + 'server_certificate': cert, + 'password': password, + } + ) class IpChange(BaseReadyChange): """ Processses IP Change. """ + name = 'ipchange' @@ -341,6 +371,7 @@ class Ready(BaseReadyChange): """ Notifies the user service is ready """ + name = 'ready' def action(self) -> typing.MutableMapping[str, typing.Any]: @@ -371,6 +402,7 @@ class Version(ActorV3Action): Notifies the version. Used on possible "customized" actors. """ + name = 'version' def action(self) -> typing.MutableMapping[str, typing.Any]: @@ -381,16 +413,21 @@ class Version(ActorV3Action): return ActorV3Action.actorResult() + class LoginLogout(ActorV3Action): name = 'notused' # Not really important, this is not a "leaf" class and will not be directly available def notifyService(self, login: bool): try: # If unmanaged, use Service locator - service : 'services.Service' = Service.objects.get(token=self._params['token']).getInstance() + service: 'services.Service' = Service.objects.get( + token=self._params['token'] + ).getInstance() # Locate an userService that belongs to this service and which # Build the possible ids and make initial filter to match service - idsList = [x['ip'] for x in self._params['id']] + [x['mac'] for x in self._params['id']][:10] + 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) @@ -399,7 +436,9 @@ class LoginLogout(ActorV3Action): raise Exception() # Check secret if is stored - storedInfo : typing.Optional[typing.MutableMapping[str, typing.Any]] = service.recoverIdInfo(validId) + storedInfo: typing.Optional[ + typing.MutableMapping[str, typing.Any] + ] = service.recoverIdInfo(validId) # If no secret valid if not storedInfo or self._params['secret'] != storedInfo['secret']: raise Exception() @@ -421,12 +460,19 @@ class Login(LoginLogout): """ Notifies user logged id """ + name = 'login' @staticmethod - def process_login(userService: UserService, username: str) -> typing.Optional[osmanagers.OSManager]: - osManager: typing.Optional[osmanagers.OSManager] = userService.getOsManagerInstance() - if not userService.in_use: # If already logged in, do not add a second login (windows does this i.e.) + def process_login( + userService: UserService, username: str + ) -> typing.Optional[osmanagers.OSManager]: + osManager: typing.Optional[ + osmanagers.OSManager + ] = userService.getOsManagerInstance() + if ( + not userService.in_use + ): # If already logged in, do not add a second login (windows does this i.e.) osmanagers.OSManager.loggedIn(userService, username) return osManager @@ -439,7 +485,9 @@ class Login(LoginLogout): try: userService: UserService = self.getUserService() - osManager = Login.process_login(userService, self._params.get('username') or '') + osManager = Login.process_login( + userService, self._params.get('username') or '' + ) maxIdle = osManager.maxIdle() if osManager else None @@ -460,28 +508,29 @@ class Login(LoginLogout): raise self.notifyService(login=True) - - return ActorV3Action.actorResult({ - 'ip': ip, - 'hostname': hostname, - 'dead_line': deadLine, - 'max_idle': maxIdle - }) + return ActorV3Action.actorResult( + {'ip': ip, 'hostname': hostname, 'dead_line': deadLine, 'max_idle': maxIdle} + ) class Logout(LoginLogout): """ Notifies user logged out """ + name = 'logout' @staticmethod - def process_logout(userService: UserService, username: str) -> None: + def process_logout(userService: UserService, username: str) -> None: """ This method is static so can be invoked from elsewhere """ - osManager: typing.Optional[osmanagers.OSManager] = userService.getOsManagerInstance() - if userService.in_use: # If already logged out, do not add a second logout (windows does this i.e.) + osManager: typing.Optional[ + osmanagers.OSManager + ] = userService.getOsManagerInstance() + if ( + userService.in_use + ): # If already logged out, do not add a second logout (windows does this i.e.) osmanagers.OSManager.loggedOut(userService, username) if osManager: if osManager.isRemovableOnLogout(userService): @@ -490,7 +539,6 @@ class Logout(LoginLogout): else: userService.remove() - def action(self) -> typing.MutableMapping[str, typing.Any]: isManaged = self._params.get('type') != UNMANAGED @@ -510,13 +558,19 @@ class Log(ActorV3Action): """ Sends a log from the service """ + name = 'log' def action(self) -> typing.MutableMapping[str, typing.Any]: logger.debug('Args: %s, Params: %s', self._args, self._params) userService = self.getUserService() # Adjust loglevel to own, we start on 10000 for OTHER, and received is 0 for OTHER - log.doLog(userService, int(self._params['level']) + 10000, self._params['message'], log.ACTOR) + log.doLog( + userService, + int(self._params['level']) + 10000, + self._params['message'], + log.ACTOR, + ) return ActorV3Action.actorResult('ok') @@ -525,6 +579,7 @@ class Ticket(ActorV3Action): """ Gets an stored ticket """ + name = 'ticket' def action(self) -> typing.MutableMapping[str, typing.Any]: @@ -532,12 +587,16 @@ class Ticket(ActorV3Action): try: # Simple check that token exists - ActorToken.objects.get(token=self._params['token']) # Not assigned, because only needs check + ActorToken.objects.get( + token=self._params['token'] + ) # Not assigned, because only needs check except ActorToken.DoesNotExist: raise BlockAccess() # If too many blocks... try: - return ActorV3Action.actorResult(TicketStore.get(self._params['ticket'], invalidate=True)) + return ActorV3Action.actorResult( + TicketStore.get(self._params['ticket'], invalidate=True) + ) except TicketStore.DoesNotExist: return ActorV3Action.actorResult(error='Invalid ticket') @@ -550,7 +609,7 @@ class Unmanaged(ActorV3Action): unmanaged 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 + * secret: Secret for commsUrl for actor (Cu * 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 @@ -570,11 +629,17 @@ class Unmanaged(ActorV3Action): # 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] + 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) + 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 @@ -583,18 +648,21 @@ class Unmanaged(ActorV3Action): cert: typing.Dict[str, str] = { 'private_key': privateKey, 'server_certificate': certificate, - 'password': password + 'password': password, } if validId: # Notify service of it "just start" action service.notifyInitialization(validId) # Store certificate, secret & port with service if validId - service.storeIdInfo(validId, { - 'cert': certificate, - 'secret': self._params['secret'], - 'port': int(self._params['port']) - }) + service.storeIdInfo( + validId, + { + 'cert': certificate, + 'secret': self._params['secret'], + 'port': int(self._params['port']), + }, + ) return ActorV3Action.actorResult(cert) @@ -608,7 +676,11 @@ class Notify(ActorV3Action): def get(self) -> typing.MutableMapping[str, typing.Any]: logger.debug('Args: %s, Params: %s', self._args, self._params) - if 'action' not in self._params or 'token' not in self._params or self._params['action'] not in ('login', 'logout'): + if ( + 'action' not in self._params + or 'token' not in self._params + or self._params['action'] not in ('login', 'logout') + ): # Requested login or logout raise RequestError('Invalid parameters') diff --git a/server/src/uds/core/services/service.py b/server/src/uds/core/services/service.py index 48da1d7b..866575e9 100644 --- a/server/src/uds/core/services/service.py +++ b/server/src/uds/core/services/service.py @@ -303,6 +303,18 @@ class Service(Module): """ return None + def lockId(self, id: str) -> bool: + """ + Locks the id, so it cannot be used by a service pool. + + Args: + id (str): Id to lock + + Returns: + bool: True if the id has been locked, False if not + """ + return False + def processLogin(self, id: str, remote_login: bool) -> None: """ In the case that a login is invoked directly on an actor controlled machine with