forked from shaba/openuds
Upgrading actor for unmanaged && fixed linux operation
This commit is contained in:
parent
6fd307e86e
commit
d1e51c0103
@ -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):
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user