def get_connect_string(self, term): """ Attached to the (server-side) `terminal:ssh_get_connect_string` WebSocket action; writes the connection string associated with *term* to the WebSocket like so:: {'terminal:sshjs_reconnect': {*term*: <connection string>}} In ssh.js we attach a WebSocket action to 'terminal:sshjs_reconnect' that assigns the connection string sent by this function to `GateOne.Terminal.terminals[*term*]['sshConnectString']`. """ # This is the first function that normally gets called when a user uses SSH # so it's a good time to update the logger with extra metadata self.ssh_log = go_logger("gateone.terminal.ssh", plugin='ssh', **self.log_metadata) term = int(term) if term not in self.loc_terms: return # Nothing to do (already closed) self.ssh_log.debug("get_connect_string()", metadata={'term': term}) connect_string = self.loc_terms[term].get('ssh_connect_string', None) if connect_string: message = { 'terminal:sshjs_reconnect': { 'term': term, 'connect_string': connect_string } } self.write_message(message)
def get_connect_string(self, term): """ Attached to the (server-side) `terminal:ssh_get_connect_string` WebSocket action; writes the connection string associated with *term* to the WebSocket like so:: {'terminal:sshjs_reconnect': {*term*: <connection string>}} In ssh.js we attach a WebSocket action to 'terminal:sshjs_reconnect' that assigns the connection string sent by this function to `GateOne.Terminal.terminals[*term*]['sshConnectString']`. """ # This is the first function that normally gets called when a user uses SSH # so it's a good time to update the logger with extra metadata self.ssh_log = go_logger( "gateone.terminal.ssh", plugin='ssh', **self.log_metadata) term = int(term) if term not in self.loc_terms: return # Nothing to do (already closed) self.ssh_log.debug("get_connect_string()", metadata={'term': term}) connect_string = self.loc_terms[term].get('ssh_connect_string', None) if connect_string: message = { 'terminal:sshjs_reconnect': { 'term': term, 'connect_string': connect_string } } self.write_message(message)
def notice_esc_seq_handler(self, message, term=None, multiplex=None): """ Handles text passed from the special optional escape sequance handler to display a *message* to the connected client (browser). It can be invoked like so: .. ansi-block:: $ echo -e "\033]_;notice|Text passed to some_function()\007" .. seealso:: :class:`app_terminal.TerminalApplication.opt_esc_handler` and :func:`terminal.Terminal._opt_handler` """ if not hasattr(self, 'notice_log'): self.notice_log = go_logger('gateone.terminal.notice', plugin='notice', **self.log_metadata) self.notice_log.info("Notice Plugin: %s" % message, metadata={ 'term': term, 'text': message }) message = "Term {term}: {message}".format(term=term, message=message) message = {'go:notice': message} self.write_message(message)
def initialize(self): """ Called inside of :meth:`TerminalApplication.initialize` shortly after the WebSocket is instantiated. """ self.ssh_log = go_logger("gateone.terminal.ssh", plugin='ssh') # NOTE: Why not use the 'Events' hook for these? You can't attach two # functions to the same event via that mechanism because it's a dict # (one would override the other). # An alternative would be to write a single function say, on_auth() that # calls both of these functions then assign it to 'terminal:authenticate' in # the 'Events' hook. I think this way is better since it is more explicit. self.on('terminal:authenticate', bind(send_ssh_css_template, self)) self.on('terminal:authenticate', bind(create_user_ssh_dir, self))
def notice_esc_seq_handler(self, message): """ Handles text passed from the special optional escape sequance handler to display a *message* to the connected client (browser). It can be invoked like so: .. ansi-block:: $ echo -e "\033]_;notice|Text passed to some_function()\007" .. seealso:: :class:`app_terminal.TerminalApplication.opt_esc_handler` and :func:`terminal.Terminal._opt_handler` """ if not hasattr(self, 'notice_log'): self.notice_log = go_logger('gateone.notice', **self.log_metadata) self.notice_log.info("Notice Plugin: %s" % message) message = {'go:notice': message} self.write_message(message)
__license__ = "AGPLv3 or Proprietary (see LICENSE.txt)" __version_info__ = (1, 0) __author__ = 'Dan McDougall <*****@*****.**>' # Standard library imports import os, io # Gate One imports from utils import RUDict, json_decode, json_encode, get_translation from golog import go_logger # 3rd party imports from tornado.options import options APPLICATION_PATH = os.path.split(__file__)[0] # Path to our application term_log = go_logger("gateone.terminal") # Localization support _ = get_translation() def save_term_settings(term, location, session, settings): """ Saves the *settings* associated with the given *term*, *location*, and *session* in the 'term_settings.json' file inside the user's session directory. When complete the given *callback* will be called (if given). """ term = str(term) # JSON wants strings as keys term_settings = RUDict() term_settings[location] = {term: settings}
from functools import partial # Our stuff from gateone import BaseHandler from utils import get_translation, mkdir_p, shell_command, which from utils import noop, bind from golog import go_logger _ = get_translation() # Tornado stuff import tornado.web import tornado.ioloop # Globals ssh_log = go_logger("gateone.terminal.ssh", plugin='ssh') OPENSSH_VERSION = None DROPBEAR_VERSION = None PLUGIN_PATH = os.path.split(__file__)[0] # Path to this plugin's directory OPEN_SUBCHANNELS = {} SUBCHANNEL_TIMEOUT = timedelta( minutes=5) # How long to wait before auto-closing READY_STRING = "GATEONE_SSH_EXEC_CMD_CHANNEL_READY" READY_MATCH = re.compile("^%s$" % READY_STRING, re.MULTILINE) OUTPUT_MATCH = re.compile("^{rs}.+^{rs}$".format(rs=READY_STRING), re.MULTILINE | re.DOTALL) VALID_PRIVATE_KEY = valid = re.compile( r'^-----BEGIN [A-Z]+ PRIVATE KEY-----.*-----END [A-Z]+ PRIVATE KEY-----$', re.MULTILINE | re.DOTALL) TIMER = None # Used to store temporary, cancellable timeouts
def authenticate(self): """ This gets called immediately after the user is authenticated successfully at the end of :meth:`ApplicationWebSocket.authenticate`. Sends all plugin JavaScript files to the client and triggers the 'example:authenticate' event. """ example_log.debug('ExampleApplication.authenticate()') # This is the log metadata that was mentioned near the top of this file. # This log_metadata will be JSON-encoded and included in all log # messages that use `self.example_log` which is super useful when # you need to parse logs at some later date and want to know the # circumstances surrounding any given message. self.log_metadata = { 'upn': self.current_user['upn'], 'ip_address': self.ws.request.remote_ip, # If your app uses the location feature make sure to include it: 'location': self.ws.location } self.example_log = go_logger("gateone.example", **self.log_metadata) # NOTE: To include even *more* metadata in a log message on a one-time # basis simply pass the metadata to the logger like so: # self.example_log("Some log message", metadata={'foo': 'bar'}) # That will ensure that {'foo': 'bar'} is included in the JSON portion # Assign our user-specific settings/policies for quick reference self.policy = applicable_policies('example', self.current_user, self.ws.prefs) # NOTE: The applicable_policies() function *is* memoized but the above # is still much faster. # Start by determining if the user can even use this app if 'allow' in self.policy: # This is the same check inside example_policies(). Why put it here # too? So we can skip sending the client JS/CSS that they won't be # able to use. if not self.policy['allow']: # User is not allowed to access this application. Don't # bother sending them any static files and whatnot... self.example_log.debug( _("User is not allowed to use the Example application. " "Skipping post-authentication functions.")) return # Render and send the client our example.css example_css = os.path.join(APPLICATION_PATH, 'templates', 'example.css') self.render_and_send_css(example_css, element_id="example.css") # NOTE: See the Gate One docs for gateone.py to see how # render_and_send_css() works. It auto-minifies and caches! # Send the client our application's static JavaScript files static_dir = os.path.join(APPLICATION_PATH, 'static') js_files = [] if os.path.isdir(static_dir): js_files = os.listdir(static_dir) # Everything in static/*.js js_files.sort() for fname in js_files: if fname.endswith('.js'): js_file_path = os.path.join(static_dir, fname) # This is notable: To ensure that all your JavaScript files # get loaded *after* example.js we add 'example.js' as a # dependency for all JS files we send to the client. if fname == 'example.js': # Adding CSS as a dependency to your app's JS is also a # good idea. You could also put 'theme.css' if you want to # ensure that the theme gets loaded before your JavaScript # init() function is called. self.send_js(js_file_path, requires=["example.css"]) else: # Send any other discovered JS files to the client with # example.js as the only dependency. self.send_js(js_file_path, requires='example.js') # If you're not using plugins you can disregard this: # The send_plugin_static_files() function will locate any JS/CSS files # in your plugins' respective static directories and send them to the # client. It is also smart enough to know which plugins are enabled # or disabled. self.ws.send_plugin_static_files(os.path.join(APPLICATION_PATH, 'plugins'), application="example", requires=["example.js"]) sess = SESSIONS[self.ws.session] # A shortcut to save some typing # Create a place to store app-specific stuff related to this session # (but not necessarily this 'location') if "example" not in sess: sess['example'] = {} # A mostly persistent place to store info # If you want to call a function whenever Gate One exits just add it # to SESSIONS[self.ws.session]["kill_session_callbacks"] like so: #if kill_session_func not in sess["kill_session_callbacks"]: #sess["kill_session_callbacks"].append(kill_session_func) # If you want to call a function whenever a user's session times out # just attach it to SESSIONS[self.ws.session]["timeout_callbacks"] # like so: #if timeout_session_func not in sess["timeout_callbacks"]: #sess["timeout_callbacks"].append(timeout_session_func) # NOTE: The user will often be authenticated before example.js is # loaded. In fact, the only time this won't be the case is when the # user is disconnected (e.g. server restart) and then reconnects. self.trigger("example:authenticate")
from functools import partial # Our stuff from gateone import BaseHandler from utils import get_translation, mkdir_p, shell_command, which from utils import noop, bind from golog import go_logger _ = get_translation() # Tornado stuff import tornado.web import tornado.ioloop # Globals ssh_log = go_logger("gateone.terminal.ssh", plugin='ssh') OPENSSH_VERSION = None DROPBEAR_VERSION = None PLUGIN_PATH = os.path.split(__file__)[0] # Path to this plugin's directory OPEN_SUBCHANNELS = {} SUBCHANNEL_TIMEOUT = timedelta(minutes=5) # How long to wait before auto-closing READY_STRING = "GATEONE_SSH_EXEC_CMD_CHANNEL_READY" READY_MATCH = re.compile("^%s$" % READY_STRING, re.MULTILINE) OUTPUT_MATCH = re.compile( "^{rs}.+^{rs}$".format(rs=READY_STRING), re.MULTILINE|re.DOTALL) VALID_PRIVATE_KEY = valid = re.compile( r'^-----BEGIN [A-Z]+ PRIVATE KEY-----.*-----END [A-Z]+ PRIVATE KEY-----$', re.MULTILINE|re.DOTALL) TIMER = None # Used to store temporary, cancellable timeouts # TODO: make execute_command() a user-configurable option... So it will automatically run whatever command(s) the user likes whenever they connect to a given server. Differentiate between when they connect and when they start up a master or slave channel. # TODO: Make it so that equivalent KnownHostsHandler functionality works over the WebSocket.
from golog import go_logger # 3rd party imports import tornado.web import tornado.auth import tornado.escape # Localization support _ = get_translation() # Globals GATEONE_DIR = os.path.dirname(os.path.abspath(__file__)) SETTINGS_CACHE = {} # Lists of settings files and their modification times # The security stuff below is a work-in-progress. Likely to change all around. auth_log = go_logger('gateone.auth') # Authorization stuff @memoize def applicable_policies(application, user, policies): """ Given an *application* and a *user* object, returns the merged/resolved policies from the given *policies* :class:`RUDict`. .. note:: Policy settings always start with '*', 'user', or 'group'. """ # Start with the default policy try: policy = RUDict(policies['*'][application].copy()) except KeyError:
def authenticate(self): """ This gets called immediately after the user is authenticated successfully at the end of :meth:`ApplicationWebSocket.authenticate`. Sends all plugin JavaScript files to the client and triggers the 'example:authenticate' event. """ example_log.debug('ExampleApplication.authenticate()') # This is the log metadata that was mentioned near the top of this file. # This log_metadata will be JSON-encoded and included in all log # messages that use `self.example_log` which is super useful when # you need to parse logs at some later date and want to know the # circumstances surrounding any given message. self.log_metadata = { 'upn': self.current_user['upn'], 'ip_address': self.ws.request.remote_ip, # If your app uses the location feature make sure to include it: 'location': self.ws.location } self.example_log = go_logger("gateone.example", **self.log_metadata) # NOTE: To include even *more* metadata in a log message on a one-time # basis simply pass the metadata to the logger like so: # self.example_log("Some log message", metadata={'foo': 'bar'}) # That will ensure that {'foo': 'bar'} is included in the JSON portion # Assign our user-specific settings/policies for quick reference self.policy = applicable_policies( 'example', self.current_user, self.ws.prefs) # NOTE: The applicable_policies() function *is* memoized but the above # is still much faster. # Start by determining if the user can even use this app if 'allow' in self.policy: # This is the same check inside example_policies(). Why put it here # too? So we can skip sending the client JS/CSS that they won't be # able to use. if not self.policy['allow']: # User is not allowed to access this application. Don't # bother sending them any static files and whatnot... self.example_log.debug(_( "User is not allowed to use the Example application. " "Skipping post-authentication functions.")) return # Render and send the client our example.css example_css = os.path.join( APPLICATION_PATH, 'templates', 'example.css') self.render_and_send_css(example_css, element_id="example.css") # NOTE: See the Gate One docs for gateone.py to see how # render_and_send_css() works. It auto-minifies and caches! # Send the client our application's static JavaScript files static_dir = os.path.join(APPLICATION_PATH, 'static') js_files = os.listdir(static_dir) # Everything in static/*.js js_files.sort() for fname in js_files: if fname.endswith('.js'): js_file_path = os.path.join(static_dir, fname) # This is notable: To ensure that all your JavaScript files # get loaded *after* example.js we add 'example.js' as a # dependency for all JS files we send to the client. if fname == 'example.js': # Adding CSS as a dependency to your app's JS is also a # good idea. You could also put 'theme.css' if you want to # ensure that the theme gets loaded before your JavaScript # init() function is called. self.ws.send_js(js_file_path, requires=["example.css"]) else: # Send any other discovered JS files to the client with # example.js as the only dependency. self.ws.send_js(js_file_path, requires='example.js') # If you're not using plugins you can disregard this: # The send_plugin_static_files() function will locate any JS/CSS files # in your plugins' respective static directories and send them to the # client. It is also smart enough to know which plugins are enabled # or disabled. self.ws.send_plugin_static_files( os.path.join(APPLICATION_PATH, 'plugins'), application="example", requires=["example.js"]) sess = SESSIONS[self.ws.session] # A shortcut to save some typing # Create a place to store app-specific stuff related to this session # (but not necessarily this 'location') if "example" not in sess: sess['example'] = {} # A mostly persistent place to store info # If you want to call a function whenever Gate One exits just add it # to SESSIONS[self.ws.session]["kill_session_callbacks"] like so: #if kill_session_func not in sess["kill_session_callbacks"]: #sess["kill_session_callbacks"].append(kill_session_func) # If you want to call a function whenever a user's session times out # just attach it to SESSIONS[self.ws.session]["timeout_callbacks"] # like so: #if timeout_session_func not in sess["timeout_callbacks"]: #sess["timeout_callbacks"].append(timeout_session_func) # NOTE: The user will often be authenticated before example.js is # loaded. In fact, the only time this won't be the case is when the # user is disconnected (e.g. server restart) and then reconnects. self.trigger("example:authenticate")
# gettext stuff if you want: from utils import get_translation # 3rd party imports # You can add command line options to Gate One with define(): from tornado.options import define, options # You need 'options' to get define()'d values # Globals SESSIONS = {} # This will get replaced with gateone.py's SESSIONS dict # NOTE: The overwriting of SESSIONS happens inside of gateone.py as part of # the application initialization process. APPLICATION_PATH = os.path.split(__file__)[0] # Path to our application web_handlers = [] # Populated at the bottom of this file example_log = go_logger("gateone.example") # Our app's logger # NOTE: You can pass additional metadata to logs which will be JSON-encoded # when your messages are logged. Examples of how to do this are further along # in this file... # Localization support _ = get_translation() # You don't *have* to do this but it is a good idea # This is how you add command-line options to Gate One: define( "example_option", # NOTE: underscores are the preferred word separator here default=True, help=_("Doesn't do anything (just an example from the example app).") ) # You could then reference this option like so: # print(options.example_option)
from golog import go_logger # 3rd party imports import tornado.web import tornado.auth import tornado.escape # Localization support _ = get_translation() # Globals GATEONE_DIR = os.path.dirname(os.path.abspath(__file__)) SETTINGS_CACHE = {} # Lists of settings files and their modification times # The security stuff below is a work-in-progress. Likely to change all around. auth_log = go_logger('gateone.auth') # Authorization stuff @memoize def applicable_policies(application, user, policies): """ Given an *application* and a *user* object, returns the merged/resolved policies from the given *policies* :class:`RUDict`. .. note:: Policy settings always start with '*', 'user', or 'group'. """ # Start with the default policy try: policy = RUDict(policies['*'][application].copy()) except KeyError: # No default policy--not good but not mandatory
# You can use this for providing localization but you could just use the stdlib # gettext stuff if you want: from utils import get_translation # 3rd party imports # You can add command line options to Gate One with define(): from tornado.options import define, options # You need 'options' to get define()'d values # Globals SESSIONS = {} # This will get replaced with gateone.py's SESSIONS dict # NOTE: The overwriting of SESSIONS happens inside of gateone.py as part of # the application initialization process. APPLICATION_PATH = os.path.split(__file__)[0] # Path to our application web_handlers = [] # Populated at the bottom of this file example_log = go_logger("gateone.example") # Our app's logger # NOTE: You can pass additional metadata to logs which will be JSON-encoded # when your messages are logged. Examples of how to do this are further along # in this file... # Localization support _ = get_translation() # You don't *have* to do this but it is a good idea # This is how you add command-line options to Gate One: define( "example_option", # NOTE: underscores are the preferred word separator here default=True, help=_("Doesn't do anything (just an example from the example app).")) # You could then reference this option like so: # print(options.example_option)
from golog import go_logger # 3rd party imports import tornado.web import tornado.auth import tornado.escape # Localization support _ = get_translation() # Globals GATEONE_DIR = os.path.dirname(os.path.abspath(__file__)) SETTINGS_CACHE = {} # Lists of settings files and their modification times # The security stuff below is a work-in-progress. Likely to change all around. auth_log = go_logger("gateone.auth") # Authorization stuff @memoize def applicable_policies(application, user, policies): """ Given an *application* and a *user* object, returns the merged/resolved policies from the given *policies* :class:`RUDict`. .. note:: Policy settings always start with '*', 'user', or 'group'. """ # Start with the default policy try: policy = RUDict(policies["*"][application].copy()) except KeyError: # No default policy--not good but not mandatory
__license__ = "AGPLv3 or Proprietary (see LICENSE.txt)" __version_info__ = (1, 0) __author__ = 'Dan McDougall <*****@*****.**>' # Standard library imports import os, io # Gate One imports from utils import RUDict, json_decode, json_encode, get_translation from golog import go_logger # 3rd party imports from tornado.options import options APPLICATION_PATH = os.path.split(__file__)[0] # Path to our application term_log = go_logger("gateone.terminal") # Localization support _ = get_translation() def save_term_settings(term, location, session, settings): """ Saves the *settings* associated with the given *term*, *location*, and *session* in the 'term_settings.json' file inside the user's session directory. When complete the given *callback* will be called (if given). """ term = str(term) # JSON wants strings as keys term_settings = RUDict()