#913: OGAgent uses decorator to check autorization header and cleaning the Python code.

remotes/github/oglive
Ramón M. Gómez 2019-05-22 11:20:56 +02:00
parent feb481a8b3
commit ed55bec0a4
2 changed files with 98 additions and 69 deletions

View File

@ -25,14 +25,12 @@
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''' """
@author: Ramón M. Gómez, ramongomez at us dot es @author: Ramón M. Gómez, ramongomez at us dot es
''' """
from __future__ import unicode_literals from __future__ import unicode_literals
import subprocess
import threading import threading
import thread
import os import os
import platform import platform
import time import time
@ -48,8 +46,27 @@ from opengnsys.log import logger
from opengnsys.scriptThread import ScriptExecutorThread from opengnsys.scriptThread import ScriptExecutorThread
# Check authorization header decorator
def check_secret(fnc):
"""
Decorator to check for received secret key and raise exception if it isn't valid.
"""
def wrapper(*args, **kwargs):
try:
this, path, get_params, post_params, server = args # @UnusedVariable
if this.random == server.headers['Authorization']:
fnc(*args, **kwargs)
else:
raise Exception('Unauthorized operation')
except Exception as e:
logger.error(e)
raise Exception(e)
return wrapper
# Error handler decorator. # Error handler decorator.
def catchBackgroundError(fnc): def catch_background_error(fnc):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
this = args[0] this = args[0]
try: try:
@ -62,22 +79,12 @@ def catchBackgroundError(fnc):
class OpenGnSysWorker(ServerWorker): class OpenGnSysWorker(ServerWorker):
name = 'opengnsys' name = 'opengnsys'
interface = None # Bound interface for OpenGnsys interface = None # Bound interface for OpenGnsys
loggedin = False # User session flag REST = None # REST object
logged_in = False # User session flag
locked = {} locked = {}
random = None # Random string for secure connections random = None # Random string for secure connections
length = 32 # Random string length length = 32 # Random string length
def checkSecret(self, server):
"""
Checks for received secret key and raise exception if it isn't valid.
"""
try:
if self.random != server.headers['Authorization']:
raise Exception('Unauthorized operation')
except Exception as e:
logger.error(e)
raise Exception(e)
def onActivation(self): def onActivation(self):
""" """
Sends OGAgent activation notification to OpenGnsys server Sends OGAgent activation notification to OpenGnsys server
@ -86,7 +93,8 @@ class OpenGnSysWorker(ServerWorker):
# Generate random secret to send on activation # Generate random secret to send on activation
self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length)) self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length))
# Ensure cfg has required configuration variables or an exception will be thrown # Ensure cfg has required configuration variables or an exception will be thrown
self.REST = REST(self.service.config.get('opengnsys', 'remote')) url = self.service.config.get('opengnsys', 'remote')
self.REST = REST(url)
# Get network interfaces until they are active or timeout (5 minutes) # Get network interfaces until they are active or timeout (5 minutes)
for t in range(0, 300): for t in range(0, 300):
try: try:
@ -108,21 +116,22 @@ class OpenGnSysWorker(ServerWorker):
try: try:
try: try:
self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip,
'secret': self.random, 'ostype': operations.osType, 'secret': self.random, 'ostype': operations.os_type,
'osversion': operations.osVersion}) 'osversion': operations.os_version})
break break
except: except:
# Trying to initialize on alternative server, if defined # Trying to initialize on alternative server, if defined
# (used in "exam mode" from the University of Seville) # (used in "exam mode" from the University of Seville)
self.REST = REST(self.service.config.get('opengnsys', 'altremote')) self.REST = REST(self.service.config.get('opengnsys', 'altremote'))
self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip,
'secret': self.random, 'ostype': operations.osType, 'secret': self.random, 'ostype': operations.os_type,
'osversion': operations.osVersion, 'alt_url': True}) 'osversion': operations.os_version, 'alt_url': True})
break break
except: except:
time.sleep(3) time.sleep(3)
# Raise error after timeout
if 0 < t < 100: if 0 < t < 100:
logger.debug("Successful connection after {} tries".format(t)) logger.debug('Successful connection after {} tries'.format(t))
elif t == 100: elif t == 100:
raise Exception('Initialization error: Cannot connect to remote server') raise Exception('Initialization error: Cannot connect to remote server')
# Delete marking files # Delete marking files
@ -133,10 +142,10 @@ class OpenGnSysWorker(ServerWorker):
pass pass
# Copy file "HostsFile.FirstOctetOfIPAddress" to "HostsFile", if it exists # Copy file "HostsFile.FirstOctetOfIPAddress" to "HostsFile", if it exists
# (used in "exam mode" from the University of Seville) # (used in "exam mode" from the University of Seville)
hostsFile = os.path.join(operations.get_etc_path(), 'hosts') hosts_file = os.path.join(operations.get_etc_path(), 'hosts')
newHostsFile = hostsFile + '.' + self.interface.ip.split('.')[0] new_hosts_file = hosts_file + '.' + self.interface.ip.split('.')[0]
if os.path.isfile(newHostsFile): if os.path.isfile(new_hosts_file):
shutil.copyfile(newHostsFile, hostsFile) shutil.copyfile(new_hosts_file, hosts_file)
def onDeactivation(self): def onDeactivation(self):
""" """
@ -144,7 +153,7 @@ class OpenGnSysWorker(ServerWorker):
""" """
logger.debug('onDeactivation') logger.debug('onDeactivation')
self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip, self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip,
'ostype': operations.osType, 'osversion': operations.osVersion}) 'ostype': operations.os_type, 'osversion': operations.os_version})
def processClientMessage(self, message, data): def processClientMessage(self, message, data):
logger.debug('Got OpenGnsys message from client: {}, data {}'.format(message, data)) logger.debug('Got OpenGnsys message from client: {}, data {}'.format(message, data))
@ -155,19 +164,19 @@ class OpenGnSysWorker(ServerWorker):
""" """
user, sep, language = data.partition(',') user, sep, language = data.partition(',')
logger.debug('Received login for {} with language {}'.format(user, language)) logger.debug('Received login for {} with language {}'.format(user, language))
self.loggedin = True self.logged_in = True
self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language, self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language,
'ostype': operations.osType, 'osversion': operations.osVersion}) 'ostype': operations.os_type, 'osversion': operations.os_version})
def onLogout(self, user): def onLogout(self, user):
""" """
Sends session logout notification to OpenGnsys server Sends session logout notification to OpenGnsys server
""" """
logger.debug('Received logout for {}'.format(user)) logger.debug('Received logout for {}'.format(user))
self.loggedin = False self.logged_in = False
self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user}) self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user})
def process_ogclient(self, path, getParams, postParams, server): def process_ogclient(self, path, get_params, post_params, server):
""" """
This method can be overridden to provide your own message processor, or better you can This method can be overridden to provide your own message processor, or better you can
implement a method that is called exactly as "process_" + path[0] (module name has been removed from path implement a method that is called exactly as "process_" + path[0] (module name has been removed from path
@ -175,11 +184,11 @@ class OpenGnSysWorker(ServerWorker):
* Example: * Example:
Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z
The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this: The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this:
module.processMessage(["mazinger","Z"], getParams, postParams) module.processMessage(["mazinger","Z"], get_params, post_params)
This method will process "mazinger", and look for a "self" method that is called "process_mazinger", This method will process "mazinger", and look for a "self" method that is called "process_mazinger",
and invoke it this way: and invoke it this way:
return self.process_mazinger(["Z"], getParams, postParams) return self.process_mazinger(["Z"], get_params, post_params)
In the case path is empty (that is, the path is composed only by the module name, like in In the case path is empty (that is, the path is composed only by the module name, like in
"http://example.com/Sample", the "process" method will be invoked directly "http://example.com/Sample", the "process" method will be invoked directly
@ -193,13 +202,18 @@ class OpenGnSysWorker(ServerWorker):
operation = getattr(self, 'ogclient_' + path[0]) operation = getattr(self, 'ogclient_' + path[0])
except Exception: except Exception:
raise Exception('Message processor for "{}" not found'.format(path[0])) raise Exception('Message processor for "{}" not found'.format(path[0]))
return operation(path[1:], getParams, postParams) return operation(path[1:], get_params, post_params)
def process_status(self, path, getParams, postParams, server): def process_status(self, path, get_params, post_params, server):
""" """
Returns client status. Returns client status (OS type or execution status) and login status
:param path:
:param get_params:
:param post_params:
:param server:
:return: JSON object {"status": "status_code", "loggedin": boolean}
""" """
res = {'status': '', 'loggedin': self.loggedin} res = {'status': '', 'loggedin': self.logged_in}
if platform.system() == 'Linux': # GNU/Linux if platform.system() == 'Linux': # GNU/Linux
# Check if it's OpenGnsys Client. # Check if it's OpenGnsys Client.
if os.path.exists('/scripts/oginit'): if os.path.exists('/scripts/oginit'):
@ -218,68 +232,83 @@ class OpenGnSysWorker(ServerWorker):
res['status'] = 'OSX' res['status'] = 'OSX'
return res return res
def process_reboot(self, path, getParams, postParams, server): @check_secret
def process_reboot(self, path, get_params, post_params, server):
""" """
Launches a system reboot operation. Launches a system reboot operation
:param path:
:param get_params:
:param post_params:
:param server: authorization header
:return: JSON object {"op": "launched"}
""" """
logger.debug('Received reboot operation') logger.debug('Received reboot operation')
self.checkSecret(server)
# Rebooting thread. # Rebooting thread
def rebt(): def rebt():
operations.reboot() operations.reboot()
threading.Thread(target=rebt).start() threading.Thread(target=rebt).start()
return {'op': 'launched'} return {'op': 'launched'}
def process_poweroff(self, path, getParams, postParams, server): @check_secret
def process_poweroff(self, path, get_params, post_params, server):
""" """
Launches a system power off operation. Launches a system power off operation
:param path:
:param get_params:
:param post_params:
:param server: authorization header
:return: JSON object {"op": "launched"}
""" """
logger.debug('Received poweroff operation') logger.debug('Received poweroff operation')
self.checkSecret(server)
# Powering off thread. # Powering off thread
def pwoff(): def pwoff():
time.sleep(2) time.sleep(2)
operations.poweroff() operations.poweroff()
threading.Thread(target=pwoff).start() threading.Thread(target=pwoff).start()
return {'op': 'launched'} return {'op': 'launched'}
def process_script(self, path, getParams, postParams, server): @check_secret
def process_script(self, path, get_params, post_params, server):
""" """
Processes an script execution (script should be encoded in base64) Processes an script execution (script should be encoded in base64)
:param path:
:param get_params:
:param post_params: JSON object {"script": "commands"}
:param server: authorization header
:return: JSON object {"op": "launched"}
""" """
logger.debug('Processing script request') logger.debug('Processing script request')
self.checkSecret(server) # Decoding script
# Decoding script. script = urllib.unquote(post_params.get('script').decode('base64')).decode('utf8')
script = urllib.unquote(postParams.get('script').decode('base64')).decode('utf8')
script = 'import subprocess; subprocess.check_output("""{}""",shell=True)'.format(script) script = 'import subprocess; subprocess.check_output("""{}""",shell=True)'.format(script)
# Executing script. # Executing script.
if postParams.get('client', 'false') == 'false': if post_params.get('client', 'false') == 'false':
thr = ScriptExecutorThread(script) thr = ScriptExecutorThread(script)
thr.start() thr.start()
else: else:
self.sendClientMessage('script', {'code': script}) self.sendClientMessage('script', {'code': script})
return {'op': 'launched'} return {'op': 'launched'}
def process_logoff(self, path, getParams, postParams, server): @check_secret
def process_logoff(self, path, get_params, post_params, server):
""" """
Closes user session. Closes user session
""" """
logger.debug('Received logoff operation') logger.debug('Received logoff operation')
self.checkSecret(server) # Sending log off message to OGAgent client
# Sending log off message to OGAgent client.
self.sendClientMessage('logoff', {}) self.sendClientMessage('logoff', {})
return {'op': 'sent to client'} return {'op': 'sent to client'}
def process_popup(self, path, getParams, postParams, server): @check_secret
def process_popup(self, path, get_params, post_params, server):
""" """
Shows a message popup on the user's session. Shows a message popup on the user's session
""" """
logger.debug('Received message operation') logger.debug('Received message operation')
self.checkSecret(server) # Sending popup message to OGAgent client
# Sending popup message to OGAgent client. self.sendClientMessage('popup', post_params)
self.sendClientMessage('popup', postParams)
return {'op': 'launched'} return {'op': 'launched'}
def process_client_popup(self, params): def process_client_popup(self, params):

View File

@ -26,25 +26,25 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''' """
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
''' """
# pylint: disable=unused-wildcard-import,wildcard-import # pylint: disable=unused-wildcard-import,wildcard-import
from __future__ import unicode_literals from __future__ import unicode_literals
import sys import sys
# Importing platform operations and getting operating system data. # Importing platform operations and getting operating system data.
if sys.platform == 'win32': if sys.platform == 'win32':
from .windows.operations import * # @UnusedWildImport from .windows.operations import * # @UnusedWildImport
osType = 'Windows' os_type = 'Windows'
osVersion = getWindowsVersion() os_version = getWindowsVersion()
else: else:
if sys.platform == 'darwin': if sys.platform == 'darwin':
from .macos.operations import * # @UnusedWildImport from .macos.operations import * # @UnusedWildImport
osType = 'MacOS' os_type = 'MacOS'
osVersion = getMacosVersion().replace(',','') os_version = getMacosVersion().replace(',', '')
else: else:
from .linux.operations import * # @UnusedWildImport from .linux.operations import * # @UnusedWildImport
osType = 'Linux' os_type = 'Linux'
osVersion = getLinuxVersion().replace(',','') os_version = getLinuxVersion().replace(',', '')