Upgrading actor for unmanaged && fixed linux operation

This commit is contained in:
Adolfo Gómez García 2021-07-19 13:26:36 +02:00
parent 6fd307e86e
commit d1e51c0103
3 changed files with 144 additions and 60 deletions

View File

@ -91,12 +91,12 @@ def _getInterfaces() -> typing.List[str]:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
names = array.array(str('B'), b'\0' * space) names = array.array(str('B'), b'\0' * space)
outbytes = struct.unpack(str('iL'), fcntl.ioctl( outbytes = struct.unpack('iL', fcntl.ioctl(
s.fileno(), s.fileno(),
0x8912, # SIOCGIFCONF 0x8912, # SIOCGIFCONF
struct.pack(str('iL'), space, names.buffer_info()[0]) struct.pack('iL', space, names.buffer_info()[0])
))[0] ))[0]
namestr = names.tostring() namestr = names.tobytes()
# return namestr, outbytes # return namestr, outbytes
return [namestr[i:i + offset].split(b'\0', 1)[0].decode('utf-8') for i in range(0, outbytes, length)] 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 Returns True if reboot needed
''' '''
rename(newName) 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): def joinDomain(domain: str, ou: str, account: str, password: str, executeInOneStep: bool = False):

View File

@ -64,6 +64,7 @@ UNMANAGED = 'unmanaged' # matches the definition of UDS Actors OFC
class BlockAccess(Exception): class BlockAccess(Exception):
pass pass
# Helpers # Helpers
@ -73,7 +74,11 @@ def checkBlockedIp(ip: str) -> None:
cache = Cache('actorv3') cache = Cache('actorv3')
fails = cache.get(ip) or 0 fails = cache.get(ip) or 0
if fails > ALLOWED_FAILS: 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() raise BlockAccess()
@ -88,7 +93,9 @@ class ActorV3Action(Handler):
path = 'actor/v3' path = 'actor/v3'
@staticmethod @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 '' result = result or ''
res = {'result': result, 'stamp': getSqlDatetimeAsUnix()} res = {'result': result, 'stamp': getSqlDatetimeAsUnix()}
if error: if error:
@ -130,6 +137,7 @@ class Test(ActorV3Action):
""" """
Tests UDS Broker actor connectivity & key Tests UDS Broker actor connectivity & key
""" """
name = 'test' name = 'test'
def post(self) -> typing.MutableMapping[str, typing.Any]: def post(self) -> typing.MutableMapping[str, typing.Any]:
@ -138,7 +146,9 @@ class Test(ActorV3Action):
if self._params.get('type') == UNMANAGED: if self._params.get('type') == UNMANAGED:
Service.objects.get(token=self._params['token']) Service.objects.get(token=self._params['token'])
else: 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: except Exception:
return ActorV3Action.actorResult('invalid token') return ActorV3Action.actorResult('invalid token')
@ -149,6 +159,7 @@ class Register(ActorV3Action):
""" """
Registers an actor Registers an actor
""" """
authenticated = True authenticated = True
needs_staff = True needs_staff = True
@ -182,16 +193,17 @@ class Register(ActorV3Action):
runonce_command=self._params['run_once_command'], runonce_command=self._params['run_once_command'],
log_level=self._params['log_level'], log_level=self._params['log_level'],
token=secrets.token_urlsafe(36), token=secrets.token_urlsafe(36),
stamp=getSqlDatetime() stamp=getSqlDatetime(),
) )
return ActorV3Action.actorResult(actorToken.token) return ActorV3Action.actorResult(actorToken.token)
class Initiialize(ActorV3Action): class Initialize(ActorV3Action):
""" """
Information about machine action. Information about machine action.
Also returns the id used for the rest of the actions. (Only this one will use actor key) Also returns the id used for the rest of the actions. (Only this one will use actor key)
""" """
name = 'initialize' name = 'initialize'
def action(self) -> typing.MutableMapping[str, typing.Any]: def action(self) -> typing.MutableMapping[str, typing.Any]:
@ -235,11 +247,15 @@ class Initiialize(ActorV3Action):
service: Service = Service.objects.get(token=self._params['token']) service: Service = Service.objects.get(token=self._params['token'])
# Locate an userService that belongs to this service and which # Locate an userService that belongs to this service and which
# Build the possible ids and make initial filter to match service # 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) dbFilter = UserService.objects.filter(deployed_service__service=service)
else: else:
# If not service provided token, use actor tokens # 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 # 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]] idsList = [i['mac'] for i in self._params['id'][:5]]
dbFilter = UserService.objects.all() 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. # Valid actor token, now validate access allowed. That is, look for a valid mac from the ones provided.
try: try:
userService: UserService = next( userService: UserService = next(
iter(dbFilter.filter( iter(
dbFilter.filter(
unique_id__in=idsList, unique_id__in=idsList,
state__in=[State.USABLE, State.PREPARING] state__in=[State.USABLE, State.PREPARING],
)) )
)
) )
except Exception as e: except Exception as e:
logger.info('Unmanaged host request: %s, %s', self._params, e) logger.info('Unmanaged host request: %s, %s', self._params, e)
return ActorV3Action.actorResult({ return ActorV3Action.actorResult(
'own_token': None, {'own_token': None, 'max_idle': None, 'unique_id': None, 'os': None}
'max_idle': None, )
'unique_id': None,
'os': None
})
# Managed by UDS, get initialization data from osmanager and return it # Managed by UDS, get initialization data from osmanager and return it
# Set last seen actor version # Set last seen actor version
@ -269,11 +284,13 @@ class Initiialize(ActorV3Action):
if osManager: if osManager:
osData = osManager.actorData(userService) osData = osManager.actorData(userService)
return ActorV3Action.actorResult({ return ActorV3Action.actorResult(
{
'own_token': userService.uuid, 'own_token': userService.uuid,
'unique_id': userService.unique_id, 'unique_id': userService.unique_id,
'os': osData 'os': osData,
}) }
)
except (ActorToken.DoesNotExist, Service.DoesNotExist): except (ActorToken.DoesNotExist, Service.DoesNotExist):
raise BlockAccess() raise BlockAccess()
@ -282,6 +299,7 @@ class BaseReadyChange(ActorV3Action):
""" """
Records the IP change of actor Records the IP change of actor
""" """
name = 'notused' # Not really important, this is not a "leaf" class and will not be directly available 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]: def action(self) -> typing.MutableMapping[str, typing.Any]:
@ -309,7 +327,12 @@ class BaseReadyChange(ActorV3Action):
userService.updateData(userServiceInstance) userService.updateData(userServiceInstance)
# Store communications url also # 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: if userService.os_state != State.USABLE:
userService.setOsState(State.USABLE) userService.setOsState(State.USABLE)
@ -327,13 +350,20 @@ class BaseReadyChange(ActorV3Action):
userService.setProperty('priv', privateKey) userService.setProperty('priv', privateKey)
userService.setProperty('priv_passwd', password) 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): class IpChange(BaseReadyChange):
""" """
Processses IP Change. Processses IP Change.
""" """
name = 'ipchange' name = 'ipchange'
@ -341,6 +371,7 @@ class Ready(BaseReadyChange):
""" """
Notifies the user service is ready Notifies the user service is ready
""" """
name = 'ready' name = 'ready'
def action(self) -> typing.MutableMapping[str, typing.Any]: def action(self) -> typing.MutableMapping[str, typing.Any]:
@ -371,6 +402,7 @@ class Version(ActorV3Action):
Notifies the version. Notifies the version.
Used on possible "customized" actors. Used on possible "customized" actors.
""" """
name = 'version' name = 'version'
def action(self) -> typing.MutableMapping[str, typing.Any]: def action(self) -> typing.MutableMapping[str, typing.Any]:
@ -381,16 +413,21 @@ class Version(ActorV3Action):
return ActorV3Action.actorResult() return ActorV3Action.actorResult()
class LoginLogout(ActorV3Action): class LoginLogout(ActorV3Action):
name = 'notused' # Not really important, this is not a "leaf" class and will not be directly available name = 'notused' # Not really important, this is not a "leaf" class and will not be directly available
def notifyService(self, login: bool): def notifyService(self, login: bool):
try: try:
# If unmanaged, use Service locator # 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 # Locate an userService that belongs to this service and which
# Build the possible ids and make initial filter to match service # 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) validId: typing.Optional[str] = service.getValidId(idsList)
@ -399,7 +436,9 @@ class LoginLogout(ActorV3Action):
raise Exception() raise Exception()
# Check secret if is stored # 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 no secret valid
if not storedInfo or self._params['secret'] != storedInfo['secret']: if not storedInfo or self._params['secret'] != storedInfo['secret']:
raise Exception() raise Exception()
@ -421,12 +460,19 @@ class Login(LoginLogout):
""" """
Notifies user logged id Notifies user logged id
""" """
name = 'login' name = 'login'
@staticmethod @staticmethod
def process_login(userService: UserService, username: str) -> typing.Optional[osmanagers.OSManager]: def process_login(
osManager: typing.Optional[osmanagers.OSManager] = userService.getOsManagerInstance() userService: UserService, username: str
if not userService.in_use: # If already logged in, do not add a second login (windows does this i.e.) ) -> 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) osmanagers.OSManager.loggedIn(userService, username)
return osManager return osManager
@ -439,7 +485,9 @@ class Login(LoginLogout):
try: try:
userService: UserService = self.getUserService() 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 maxIdle = osManager.maxIdle() if osManager else None
@ -460,19 +508,16 @@ class Login(LoginLogout):
raise raise
self.notifyService(login=True) self.notifyService(login=True)
return ActorV3Action.actorResult(
return ActorV3Action.actorResult({ {'ip': ip, 'hostname': hostname, 'dead_line': deadLine, 'max_idle': maxIdle}
'ip': ip, )
'hostname': hostname,
'dead_line': deadLine,
'max_idle': maxIdle
})
class Logout(LoginLogout): class Logout(LoginLogout):
""" """
Notifies user logged out Notifies user logged out
""" """
name = 'logout' name = 'logout'
@staticmethod @staticmethod
@ -480,8 +525,12 @@ class Logout(LoginLogout):
""" """
This method is static so can be invoked from elsewhere This method is static so can be invoked from elsewhere
""" """
osManager: typing.Optional[osmanagers.OSManager] = userService.getOsManagerInstance() osManager: typing.Optional[
if userService.in_use: # If already logged out, do not add a second logout (windows does this i.e.) 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) osmanagers.OSManager.loggedOut(userService, username)
if osManager: if osManager:
if osManager.isRemovableOnLogout(userService): if osManager.isRemovableOnLogout(userService):
@ -490,7 +539,6 @@ class Logout(LoginLogout):
else: else:
userService.remove() userService.remove()
def action(self) -> typing.MutableMapping[str, typing.Any]: def action(self) -> typing.MutableMapping[str, typing.Any]:
isManaged = self._params.get('type') != UNMANAGED isManaged = self._params.get('type') != UNMANAGED
@ -510,13 +558,19 @@ class Log(ActorV3Action):
""" """
Sends a log from the service Sends a log from the service
""" """
name = 'log' name = 'log'
def action(self) -> typing.MutableMapping[str, typing.Any]: def action(self) -> typing.MutableMapping[str, typing.Any]:
logger.debug('Args: %s, Params: %s', self._args, self._params) logger.debug('Args: %s, Params: %s', self._args, self._params)
userService = self.getUserService() userService = self.getUserService()
# Adjust loglevel to own, we start on 10000 for OTHER, and received is 0 for OTHER # 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') return ActorV3Action.actorResult('ok')
@ -525,6 +579,7 @@ class Ticket(ActorV3Action):
""" """
Gets an stored ticket Gets an stored ticket
""" """
name = 'ticket' name = 'ticket'
def action(self) -> typing.MutableMapping[str, typing.Any]: def action(self) -> typing.MutableMapping[str, typing.Any]:
@ -532,12 +587,16 @@ class Ticket(ActorV3Action):
try: try:
# Simple check that token exists # 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: except ActorToken.DoesNotExist:
raise BlockAccess() # If too many blocks... raise BlockAccess() # If too many blocks...
try: try:
return ActorV3Action.actorResult(TicketStore.get(self._params['ticket'], invalidate=True)) return ActorV3Action.actorResult(
TicketStore.get(self._params['ticket'], invalidate=True)
)
except TicketStore.DoesNotExist: except TicketStore.DoesNotExist:
return ActorV3Action.actorResult(error='Invalid ticket') return ActorV3Action.actorResult(error='Invalid ticket')
@ -550,7 +609,7 @@ class Unmanaged(ActorV3Action):
unmanaged method expect a json POST with this fields: unmanaged method expect a json POST with this fields:
* id: List[dict] -> List of dictionary containing ip and mac: * id: List[dict] -> List of dictionary containing ip and mac:
* token: str -> Valid Actor "master_token" (if invalid, will return an error). * 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) * 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 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 # 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 # 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) validId: typing.Optional[str] = service.getValidId(idsList)
ip: str ip: str
try: 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: except StopIteration:
ip = self._params['id'][0]['ip'] # Get first IP if no valid ip found 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] = { cert: typing.Dict[str, str] = {
'private_key': privateKey, 'private_key': privateKey,
'server_certificate': certificate, 'server_certificate': certificate,
'password': password 'password': password,
} }
if validId: if validId:
# Notify service of it "just start" action # Notify service of it "just start" action
service.notifyInitialization(validId) service.notifyInitialization(validId)
# Store certificate, secret & port with service if validId # Store certificate, secret & port with service if validId
service.storeIdInfo(validId, { service.storeIdInfo(
validId,
{
'cert': certificate, 'cert': certificate,
'secret': self._params['secret'], 'secret': self._params['secret'],
'port': int(self._params['port']) 'port': int(self._params['port']),
}) },
)
return ActorV3Action.actorResult(cert) return ActorV3Action.actorResult(cert)
@ -608,7 +676,11 @@ class Notify(ActorV3Action):
def get(self) -> typing.MutableMapping[str, typing.Any]: def get(self) -> typing.MutableMapping[str, typing.Any]:
logger.debug('Args: %s, Params: %s', self._args, self._params) 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 # Requested login or logout
raise RequestError('Invalid parameters') raise RequestError('Invalid parameters')

View File

@ -303,6 +303,18 @@ class Service(Module):
""" """
return None 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: def processLogin(self, id: str, remote_login: bool) -> None:
""" """
In the case that a login is invoked directly on an actor controlled machine with In the case that a login is invoked directly on an actor controlled machine with