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)
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):

View File

@ -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')

View File

@ -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