def display_search_result(self): """打印搜索的结果""" if self.assets is None: self.assets = self.get_my_assets() if not self.search_result: self.search_result = self.assets hostname_length = max_length( [asset.hostname for asset in self.search_result]) system_user_length = system_user_max_length(self.assets) line = '[%-4s] %-16s %-5s %-' + str(hostname_length) + 's %-' + str( system_user_length + 2) + 's ' comment_length = request.win_width - len(line % ((' ', ) * 5)) - 5 line += ('%-' + str(comment_length) + 's') self.client_channel.send( wr( title( line % ('ID', 'IP', 'Port', 'Hostname', 'Username', 'Comment')))) for index, asset in enumerate(self.search_result): system_users = '[' + ', '.join( [system_user.username for system_user in asset.system_users]) \ + ']' self.client_channel.send( wr(line % (index, asset.ip, asset.port, asset.hostname, system_users, asset.comment))) self.client_channel.send(wr(''))
def search_and_proxy(self, option, from_result=False): """搜索并登录资产""" self.search_assets(option=option, from_result=from_result) if len(self.search_result) == 1: request.asset = asset = self.search_result[0] if len(asset.system_users) == 1: system_user = asset.system_users[0] else: self.client_channel.send( wr(primary('More than one system user granted, select one'))) system_user = self.choose_system_user(asset.system_users) if system_user is None: return self.dispatch() request.system_user = system_user self.return_to_proxy(asset, system_user) elif len(self.search_result) == 0: self.client_channel.send( wr(warning('No asset match, please input again'))) return self.dispatch() else: self.client_channel.send( wr(primary('Search result is not unique, ' 'select below or search again'), after=2)) self.display_search_result() self.dispatch(twice=True)
def search_and_proxy(self, option, from_result=False): """搜索并登录资产""" self.search_assets(option=option, from_result=from_result) if len(self.search_result) == 1: request.asset = asset = self.search_result[0] if len(asset.system_users) == 1: system_user = asset.system_users[0] else: self.client_channel.send( wr(primary( 'More than one system user granted, select one'))) system_user = self.choose_system_user(asset.system_users) if system_user is None: return self.dispatch() request.system_user = system_user self.return_to_proxy(asset, system_user) elif len(self.search_result) == 0: self.client_channel.send( wr(warning('No asset match, please input again'))) return self.dispatch() else: self.client_channel.send( wr(primary('Search result is not unique, ' 'select below or search again'), after=2)) self.display_search_result() self.dispatch(twice=True)
def choose_system_user(self, system_users): """当资产上有多个授权系统用户时, 让用户二次选择""" while True: for index, system_user in enumerate(system_users): self.client_channel.send(wr('[%s] %s' % (index, system_user.username))) option = self.get_input(prompt='System user> ') if option.isdigit() and int(option) < len(system_users): system_user = system_users[int(option)] return system_user elif option in ['q', 'Q']: return None else: self.client_channel.send( wr(warning('No system user match, please input again')))
def display_asset_groups(self): """打印授权的资产组""" self.asset_groups = self.get_my_asset_groups() name_max_length = max_length([asset_group.name for asset_group in self.asset_groups], max_=20) comment_max_length = max_length([asset_group.comment for asset_group in self.asset_groups], max_=30) line = '[%-3s] %-' + str(name_max_length) + 's %-6s %-' \ + str(comment_max_length) + 's' self.client_channel.send(wr(title(line % ('ID', 'Name', 'Assets', 'Comment')))) for index, asset_group in enumerate(self.asset_groups): self.client_channel.send(wr(line % ( index, asset_group.name, asset_group.assets_amount, asset_group.comment[:comment_max_length]))) self.client_channel.send(wr(''))
def process_request(self, client, addr): rc = self.request_context({'REMOTE_ADDR': addr[0]}) rc.push() logger.info("Get ssh request from %s" % request.environ['REMOTE_ADDR']) transport = paramiko.Transport(client, gss_kex=False) try: transport.load_server_moduli() except: logger.warning('Failed to load moduli -- gex will be unsupported.') raise transport.add_server_key(SSHInterface.get_host_key()) # 将app和请求上下文传递过去, ssh_interface 处理ssh认证和建立连接 ssh_interface = SSHInterface(self, rc) try: transport.start_server(server=ssh_interface) except paramiko.SSHException: logger.warning('SSH negotiation failed.') sys.exit(1) _client_channel = transport.accept(20) g.client_channel = _client_channel if _client_channel is None: logger.warning('No ssh channel get.') sys.exit(1) if request.method == 'shell': logger.info('Client asked for a shell.') InteractiveServer(self).run() elif request.method == 'command': _client_channel.send(wr(warning('We are not support command now'))) _client_channel.close() sys.exit(2) else: _client_channel.send(wr(warning('Not support the request method'))) _client_channel.close() sys.exit(2) while True: if request.user is not None: break else: time.sleep(0.2)
def display_asset_group_asset(self, option): """打印资产组下的资产""" match = re.match(r'g(\d+)', option) if match: index = match.groups()[0] if index.isdigit() and len(self.asset_groups) > int(index): asset_group = self.asset_groups[int(index)] self.search_result = self.user_service.\ get_assets_in_group(asset_group.id) self.display_search_result() self.dispatch(twice=True) self.client_channel.send( wr(warning('No asset group match, please input again')))
def display_asset_group_asset(self, option): """打印资产组下的资产""" match = re.match(r'g(\d+)', option) if match: index = match.groups()[0] if index.isdigit() and len(self.asset_groups) > int(index): asset_group = self.asset_groups[int(index)] self.search_result = self.user_service.\ get_assets_in_group(asset_group.id) self.display_search_result() self.dispatch(twice=True) self.client_channel.send(wr(warning( 'No asset group match, please input again')))
def get_input(self, prompt='Opt> '): """实现了一个ssh input, 提示用户输入, 获取并返回""" input_data = [] parser = TtyIOParser(request.win_width, request.win_height) self.client_channel.send(wr(prompt, before=1, after=0)) while True: r, w, x = select.select([self.client_channel], [], []) if self.client_channel in r: data = self.client_channel.recv(1024) if data in self.BACKSPACE_CHAR: # If input words less than 0, should send 'BELL' if len(input_data) > 0: data = self.BACKSPACE_CHAR[data] input_data.pop() else: data = self.BELL_CHAR self.client_channel.send(data) continue if data.startswith(b'\x1b') or data in self.UNSUPPORTED_CHAR: self.client_channel.send('') continue # handle shell expect multi_char_with_enter = False if len(data) > 1 and data[-1] in self.ENTER_CHAR: self.client_channel.send(data) input_data.append(data[:-1]) multi_char_with_enter = True # If user type ENTER we should get user input if data in self.ENTER_CHAR or multi_char_with_enter: self.client_channel.send(wr('', after=2)) option = parser.parse_input(b''.join(input_data)) return option.strip() else: self.client_channel.send(data) input_data.append(data)
def display_search_result(self): """打印搜索的结果""" if self.assets is None: self.assets = self.get_my_assets() if not self.search_result: self.search_result = self.assets hostname_length = max_length([asset.hostname for asset in self.search_result]) system_user_length = system_user_max_length(self.assets) line = '[%-4s] %-16s %-5s %-' + str(hostname_length) + 's %-' + str(system_user_length+2) + 's ' comment_length = request.win_width-len(line % ((' ',) * 5)) - 5 line += ('%-' + str(comment_length) + 's') self.client_channel.send(wr(title(line % ( 'ID', 'IP', 'Port', 'Hostname', 'Username', 'Comment')))) for index, asset in enumerate(self.search_result): system_users = '[' + ', '.join( [system_user.username for system_user in asset.system_users]) \ + ']' self.client_channel.send(wr( line % (index, asset.ip, asset.port, asset.hostname, system_users, asset.comment))) self.client_channel.send(wr(''))
def get_input(self, prompt='Opt> '): """实现了一个ssh input, 提示用户输入, 获取并返回""" input_data = [] parser = TtyIOParser(request.win_width, request.win_height) self.client_channel.send(wr(prompt, before=1, after=0)) while True: data = self.client_channel.recv(1024) if data in self.BACKSPACE_CHAR: # If input words less than 0, should send 'BELL' if len(input_data) > 0: data = self.BACKSPACE_CHAR[data] input_data.pop() else: data = self.BELL_CHAR self.client_channel.send(data) continue if data.startswith(b'\x1b') or data in self.UNSUPPORTED_CHAR: self.client_channel.send('') continue # handle shell expect multi_char_with_enter = False if len(data) > 1 and data[-1] in self.ENTER_CHAR: self.client_channel.send(data) input_data.append(data[:-1]) multi_char_with_enter = True # If user type ENTER we should get user input if data in self.ENTER_CHAR or multi_char_with_enter: self.client_channel.send(wr('', after=2)) option = parser.parse_input(b''.join(input_data)) return option.strip() else: self.client_channel.send(data) input_data.append(data)
def connect(self, term=b'xterm', width=80, height=24, timeout=10): user = self.user asset = self.asset system_user = self.system_user client_channel = self.client_channel try: # Todo: win_width in request or client_channel width = int(client_channel.win_width) height = int(client_channel.win_height) except TypeError: pass if not self.validate_user_asset_permission(): logger.warning('User %s have no permission connect %s with %s' % (user.username, asset.ip, system_user.username)) return None self.ssh = ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) password, private_key = self.get_asset_auth(system_user) data = { "user": user.username, "asset": asset.ip, "system_user": system_user.username, "login_type": "ST", "date_start": time.time(), "is_failed": 0 } self.proxy_log_id = proxy_log_id = self.service.send_proxy_log(data) self.app.proxy_list[ proxy_log_id] = self.client_channel, self.backend_channel try: client_channel.send( wr('Connecting %s@%s:%s ... ' % (system_user.username, asset.ip, asset.port))) ssh.connect(hostname=asset.ip, port=asset.port, username=system_user.username, password=password, pkey=private_key, look_for_keys=False, allow_agent=True, compress=True, timeout=timeout) except (paramiko.AuthenticationException, paramiko.ssh_exception.SSHException): msg = 'Connect backend server %s failed: %s' \ % (asset.ip, 'Auth failed') logger.warning(msg) failed = True except socket.error: msg = 'Connect asset %s failed: %s' % (asset.ip, 'Timeout') logger.warning(msg) failed = True else: msg = 'Connect asset %(username)s@%(host)s:%(port)s successfully' % { 'username': system_user.username, 'host': asset.ip, 'port': asset.port } failed = False logger.info(msg) if failed: client_channel.send(wr(warning(msg + '\r\n'))) data = { "proxy_log_id": proxy_log_id, "date_finished": time.time(), "was_failed": 1 } self.service.finish_proxy_log(data) return None self.backend_channel = channel = ssh.invoke_shell(term=term, width=width, height=height) channel.settimeout(100) return channel
def proxy(self): self.backend_channel = backend_channel = self.connect() client_channel = self.client_channel if backend_channel is None: return self.app.proxy_list[self.proxy_log_id] = \ [self.client_channel, backend_channel] while not self.stop_event.set(): try: r, w, x = select.select([client_channel, backend_channel], [], []) except select.error: break #if self.change_win_size_event.is_set(): # self.change_win_size_event.clear() # width = self.client_channel.win_width # height = self.client_channel.win_height # backend_channel.resize_pty(width=width, height=height) if client_channel in r: # Get output of the command self.is_first_input = False if self.in_input_state is False: self.get_output() del self.output_data[:] self.in_input_state = True client_data = client_channel.recv(1024) if self.is_finish_input(client_data): self.in_input_state = False self.get_input() del self.input_data[:] if len(client_data) == 0: break backend_channel.send(client_data) if backend_channel in r: backend_data = backend_channel.recv(1024) if self.in_input_state: self.input_data.append(backend_data) else: self.output_data.append(backend_data) if len(backend_data) == 0: client_channel.send( wr('Disconnect from %s' % self.asset.ip)) logger.info('Logout from asset %(host)s: %(username)s' % { 'host': self.asset.ip, 'username': self.user.username, }) break client_channel.send(backend_data) # Todo: record log send # if self.is_match_ignore_command(self.input): # output = 'ignore output ...' # else: # output = backend_data # record_data = { # 'proxy_log_id': self.proxy_log_id, # 'output': output, # 'timestamp': time.time(), # } # record_queue.put(record_data) data = { "proxy_log_id": self.proxy_log_id, "date_finished": time.time(), } self.service.finish_proxy_log(data) del self.app.proxy_list[self.proxy_log_id]
def connect(self, term='xterm', width=80, height=24, timeout=10): user = self.user asset = self.asset system_user = self.system_user try: # Todo: win_width in request or client_channel width = int(self.client_channel.win_width) height = int(self.client_channel.win_height) except TypeError: pass if not self.validate_user_asset_permission(): logger.warning('User %s have no permission connect %s with %s' % (user.username, asset.ip, system_user.username)) return None self.ssh = ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) password, private_key = self.get_asset_auth(system_user) data = {"user": user.username, "asset": asset.ip, "system_user": system_user.username, "login_type": "ST", "date_start": time.time(), "is_failed": 0} self.proxy_log_id = proxy_log_id = self.service.send_proxy_log(data) self.app.proxy_list[proxy_log_id] = self.client_channel, self.backend_channel try: self.client_channel.send( wr('Connecting %s@%s:%s ... ' % (system_user.username, asset.ip, asset.port))) ssh.connect(hostname=asset.ip, port=asset.port, username=system_user.username, password=password, pkey=private_key, look_for_keys=False, allow_agent=True, compress=True, timeout=timeout) except (paramiko.AuthenticationException, paramiko.ssh_exception.SSHException): msg = 'Connect backend server %s failed: %s' \ % (asset.ip, 'Auth failed') logger.warning(msg) failed = True except socket.error: msg = 'Connect asset %s failed: %s' % (asset.ip, 'Timeout') logger.warning(msg) failed = True else: msg = 'Connect asset %(username)s@%(host)s:%(port)s successfully' % { 'username': system_user.username, 'host': asset.ip, 'port': asset.port} failed = False logger.info(msg) if failed: self.client_channel.send(wr(warning(msg+'\r\n'))) data = { "proxy_log_id": proxy_log_id, "date_finished": time.time(), "is_failed": 1 } self.service.finish_proxy_log(data) return None self.backend_channel = channel = ssh.invoke_shell( term=term, width=width, height=height) channel.settimeout(100) return channel
def proxy(self): self.backend_channel = self.connect() if self.backend_channel is None: return self.app.proxy_list[self.proxy_log_id] = \ [self.client_channel, self.backend_channel] self.sel.register(self.client_channel, selectors.EVENT_READ) self.sel.register(self.backend_channel, selectors.EVENT_READ) while not self.stop_event.set(): events = self.sel.select() # block until data coming if self.change_win_size_event.is_set(): self.change_win_size_event.clear() width = self.client_channel.win_width height = self.client_channel.win_height self.backend_channel.resize_pty(width=width, height=height) if self.client_channel in [t[0].fileobj for t in events]: # Get output of the command self.is_first_input = False if self.in_input_state is False: self.get_output() del self.output_data[:] self.in_input_state = True client_data = self.client_channel.recv(1024) if self.is_finish_input(client_data): self.in_input_state = False self.get_input() del self.input_data[:] if len(client_data) == 0: break self.backend_channel.send(client_data) if self.backend_channel in [t[0].fileobj for t in events]: backend_data = self.backend_channel.recv(1024) if self.in_input_state: self.input_data.append(backend_data) else: self.output_data.append(backend_data) if len(backend_data) == 0: self.client_channel.send( wr('Disconnect from %s' % self.asset.ip)) logger.info('Logout from asset %(host)s: %(username)s' % { 'host': self.asset.ip, 'username': self.user.username, }) break self.client_channel.send(backend_data) # Todo: record log send # if self.is_match_ignore_command(self.input): # output = 'ignore output ...' # else: # output = backend_data # record_data = { # 'proxy_log_id': self.proxy_log_id, # 'output': output, # 'timestamp': time.time(), # } # record_queue.put(record_data) data = { "proxy_log_id": self.proxy_log_id, "date_finished": time.time(), } self.service.finish_proxy_log(data) del self.app.proxy_list[self.proxy_log_id] def close(self): self.backend_channel.close() sef.sel.close()
def proxy(self): self.backend_channel = self.connect() if self.backend_channel is None: return self.app.proxy_list[self.proxy_log_id] = \ [self.client_channel, self.backend_channel] self.sel.register(self.client_channel, selectors.EVENT_READ) self.sel.register(self.backend_channel, selectors.EVENT_READ) while not self.stop_event.set(): events = self.sel.select() # block until data coming if self.change_win_size_event.is_set(): self.change_win_size_event.clear() width = self.client_channel.win_width height = self.client_channel.win_height self.backend_channel.resize_pty(width=width, height=height) if self.client_channel in [t[0].fileobj for t in events]: # Get output of the command self.is_first_input = False if self.in_input_state is False: self.get_output() del self.output_data[:] self.in_input_state = True client_data = self.client_channel.recv(1024) if self.is_finish_input(client_data): self.in_input_state = False self.get_input() del self.input_data[:] if len(client_data) == 0: break self.backend_channel.send(client_data) if self.backend_channel in [t[0].fileobj for t in events]: backend_data = self.backend_channel.recv(1024) if self.in_input_state: self.input_data.append(backend_data) else: self.output_data.append(backend_data) if len(backend_data) == 0: self.client_channel.send( wr('Disconnect from %s' % self.asset.ip)) logger.info('Logout from asset %(host)s: %(username)s' % { 'host': self.asset.ip, 'username': self.user.username, }) break self.client_channel.send(backend_data) # Todo: record log send # if self.is_match_ignore_command(self.input): # output = 'ignore output ...' # else: # output = backend_data # record_data = { # 'proxy_log_id': self.proxy_log_id, # 'output': output, # 'timestamp': time.time(), # } # record_queue.put(record_data) data = { "proxy_log_id": self.proxy_log_id, "date_finished": time.time(), } self.service.finish_proxy_log(data) del self.app.proxy_list[self.proxy_log_id] def close(self): self.backend_channel.close() self.sel.close()