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. Attaches our two `terminal:authenticate` events (to create the user's .ssh dir and send our CSS template) and ensures that the ssh_connect.py script is executable. """ ssh_connect_path = os.path.join(PLUGIN_PATH, 'scripts', 'ssh_connect.py') if os.path.exists(ssh_connect_path): import stat st = os.stat(ssh_connect_path) if not bool(st.st_mode & stat.S_IXOTH): try: os.chmod(ssh_connect_path, 0o755) except OSError: ssh_log.error(_( "Could not set %s as executable. You will need to 'chmod " "a+x' that script manually.") % ssh_connect_path) user_msg = _( "Error loading SSH plugin: The ssh_connect.py script is " "not executable. See the logs for more details.") send_msg = partial(self.ws.send_message, user_msg) events = ["terminal:authenticate", "terminal:new_terminal"] self.on(events, send_msg) 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 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 initialize(self): """ Called inside of :meth:`TerminalApplication.initialize` shortly after the WebSocket is instantiated. Attaches our two `terminal:authenticate` events (to create the user's .ssh dir and send our CSS template) and ensures that the ssh_connect.py script is executable. """ ssh_connect_path = os.path.join(PLUGIN_PATH, 'scripts', 'ssh_connect.py') if os.path.exists(ssh_connect_path): import stat st = os.stat(ssh_connect_path) if not bool(st.st_mode & stat.S_IXOTH): os.chmod(ssh_connect_path, 0o755) 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, 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 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")
# gettext stuff if you want: from gateone.core.locale 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)
# 3rd party imports import tornado.web import tornado.auth import tornado.escape import tornado.httpclient import tornado.gen from tornado.options import options # Localization support _ = get_translation() # Globals 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') # Helper functions def additional_attributes(user, settings_dir=None): """ Given a *user* dict, return a dict containing any additional attributes defined in Gate One's attribute repositories. .. note:: This function doesn't actually work yet (support for attribute repos like LDAP is forthcoming). """ # Doesn't do anything yet if not settings_dir:
__doc__ = """\ policy.py - A module containing the Terminal application's policy functions. """ # Meta __license__ = "AGPLv3 or Proprietary (see LICENSE.txt)" __author__ = 'Dan McDougall <*****@*****.**>' from functools import partial from gateone import SESSIONS from gateone.core.locale import get_translation from gateone.core.log import go_logger from gateone.auth.authorization import applicable_policies term_log = go_logger("gateone.terminal") # Localization support _ = get_translation() def policy_new_terminal(cls, policy): """ Called by :func:`terminal_policies`, returns True if the user is authorized to execute :func:`new_terminal` and applies any configured restrictions (e.g. max_dimensions). Specifically, checks to make sure the user is not in violation of their applicable policies (e.g. max_terms). """ instance = cls.instance session = instance.ws.session auth_log = instance.ws.auth_log try:
import os, sys, io, re, socket, tempfile, logging from gateone import GATEONE_DIR from .log import FACILITIES from gateone.core.log import go_logger from tornado import locale from tornado.escape import json_decode from tornado.options import define, options, Error # Locale stuff (can't use .locale since .locale uses this module) # Default to using the environment's locale with en_US fallback temp_locale = locale.get(os.environ.get('LANG', 'en_US').split('.')[0]) _ = temp_locale.translate del temp_locale logger = go_logger(None) class SettingsError(Exception): """ Raised when we encounter an error parsing .conf files in the settings dir. """ pass class RUDict(dict): """ A dict that will recursively update keys and values in a safe manner so that sub-dicts will be merged without one clobbering the other. .. note:: This class (mostly) taken from `here
# 3rd party imports import tornado.web import tornado.auth import tornado.escape import tornado.httpclient import tornado.gen from tornado.options import options # Localization support _ = get_translation() # Globals 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") # Helper functions def additional_attributes(user, settings_dir=None): """ Given a *user* dict, return a dict containing any additional attributes defined in Gate One's attribute repositories. .. note:: This function doesn't actually work yet (support for attribute repos like LDAP is forthcoming). """ # Doesn't do anything yet if not settings_dir: settings_dir = options.settings_dir
__version_info__ = (1, 0) __author__ = 'Stuart Johnson <*****@*****.**>' import os import threading from gateone.core.server import BaseHandler import tornado.escape import tornado.web from subprocess import call import datetime from gateone.core.log import go_logger import glob # Globals PLUGIN_PATH = os.path.split(__file__)[0] # Path to our plugin's directory ssh_log = go_logger("gateone.terminal.revssh", plugin='revssh') connectFilename = "connect.sh" infoFilename = "info.html" portCounterMin = 10000 portCounterMax = 19999 portCounter = portCounterMin portCounterLock = threading.Lock() RSAPathPrefix = "/tmp/rsa_" sshpyPath = "/home/admin/ssh.py" with open('/home/admin/.ssh/id_rsa.pub', 'r') as f: rsapub = f.read().rstrip() sshPort = os.environ['SSHPORT']
import os, sys, io, re, socket, tempfile, logging from gateone import GATEONE_DIR from .log import FACILITIES from gateone.core.log import go_logger from tornado import locale from tornado.escape import json_decode from tornado.options import define, options, Error # Locale stuff (can't use .locale since .locale uses this module) # Default to using the environment's locale with en_US fallback temp_locale = locale.get(os.environ.get('LANG', 'en_US').split('.')[0]) _ = temp_locale.translate del temp_locale logger = go_logger(None) comments_re = re.compile( r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE) trailing_commas_re = re.compile( r'(,)\s*}(?=([^"\\]*(\\.|"([^"\\]*\\.)*[^"\\]*"))*[^"]*$)') class SettingsError(Exception): """ Raised when we encounter an error parsing .conf files in the settings dir. """ pass class RUDict(dict):
# You can use this for providing localization but you could just use the stdlib # gettext stuff if you want: from gateone.core.locale 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)
# Standard library imports import os, io # Gate One imports from gateone.core.utils import json_encode from gateone.core.configuration import RUDict from gateone.core.locale import get_translation from gateone.core.log import go_logger # 3rd party imports from tornado.escape import json_decode 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). """ if not session: return # Just a viewer of a broadcast terminal
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 gateone.core.log 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') # Helper functions def additional_attributes(user, settings_dir=None): """ Given a *user* dict, return a dict containing any additional attributes defined in Gate One's attribute repositories. .. note:: This function doesn't actually work yet (support for attribute repos like LDAP is forthcoming). """ # Doesn't do anything yet if not settings_dir: settings_dir = os.path.join(GATEONE_DIR, 'settings')
# Our stuff from gateone.core.server import BaseHandler from gateone.core.utils import mkdir_p, shell_command, which from gateone.core.utils import noop, bind from gateone.core.locale import get_translation from gateone.core.log 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.