From c1facb939e435d0dcbe37535f02152aa09e89570 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sat, 22 Aug 2015 00:01:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96connect.py=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- connect.py | 198 +--------------------------------------------- jasset/models.py | 10 ++- jumpserver/api.py | 196 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 199 insertions(+), 205 deletions(-) diff --git a/connect.py b/connect.py index fe83afcd4..0039aa05f 100644 --- a/connect.py +++ b/connect.py @@ -5,40 +5,22 @@ import sys reload(sys) sys.setdefaultencoding('utf8') -import socket import os import re -import select import time -import paramiko -import struct -import fcntl -import signal import textwrap import getpass -import fnmatch import readline import django -import datetime from multiprocessing import Pool os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' if django.get_version() != '1.6': django.setup() -from jlog.models import Log -from jumpserver.api import CONF, BASE_DIR, ServerError, User, UserGroup, Asset, get_object +from jumpserver.api import BASE_DIR, ServerError, User, UserGroup, Asset, Jtty, get_object from jumpserver.api import CRYPTOR, logger, is_dir from jumpserver.api import BisGroup as AssetGroup -try: - import termios - import tty -except ImportError: - print '\033[1;31m仅支持类Unix系统 Only unix like supported.\033[0m' - time.sleep(3) - sys.exit() - -log_dir = os.path.join(BASE_DIR, 'logs') login_user = get_object(User, username=getpass.getuser()) @@ -57,184 +39,6 @@ def color_print(msg, color='red', exits=False): sys.exit() -class Jtty(object): - def __init__(self, user, asset): - self.chan = None - self.username = user.username - self.ip = asset.ip - self.user = user - self.asset = asset - - @staticmethod - def get_win_size(): - """ - This function use to get the size of the windows! - 获得terminal窗口大小 - """ - if 'TIOCGWINSZ' in dir(termios): - TIOCGWINSZ = termios.TIOCGWINSZ - else: - TIOCGWINSZ = 1074295912L - s = struct.pack('HHHH', 0, 0, 0, 0) - x = fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, s) - return struct.unpack('HHHH', x)[0:2] - - def set_win_size(self, sig, data): - """ - This function use to set the window size of the terminal! - 设置terminal窗口大小 - """ - try: - win_size = self.get_win_size() - self.chan.resize_pty(height=win_size[0], width=win_size[1]) - except Exception: - pass - - def log_record(self): - """ - Logging user command and output. - 记录用户的日志 - """ - tty_log_dir = os.path.join(log_dir, 'tty') - timestamp_start = int(time.time()) - date_start = time.strftime('%Y%m%d', time.localtime(timestamp_start)) - time_start = time.strftime('%H%M%S', time.localtime(timestamp_start)) - log_filename = '%s_%s_%s.log' % (self.username, self.ip, time_start) - today_connect_log_dir = os.path.join(tty_log_dir, date_start) - log_file_path = os.path.join(today_connect_log_dir, log_filename) - dept_name = self.user.dept.name - - pid = os.getpid() - pts = os.popen("ps axu | grep %s | grep -v grep | awk '{ print $7 }'" % pid).read().strip() - ip_list = os.popen("who | grep %s | awk '{ print $5 }'" % pts).read().strip('()\n') - - try: - is_dir(today_connect_log_dir) - except OSError: - raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_log_dir)) - - try: - log_file = open(log_file_path, 'a') - except IOError: - raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir) - - log = Log(user=self.username, host=self.ip, remote_ip=ip_list, dept_name=dept_name, - log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid) - log_file.write('Start time is %s\n' % datetime.datetime.now()) - log.save() - return log_file, log - - def posix_shell(self): - """ - Use paramiko channel connect server interactive. - 使用paramiko模块的channel,连接后端,进入交互式 - """ - log_file, log = self.log_record() - old_tty = termios.tcgetattr(sys.stdin) - try: - tty.setraw(sys.stdin.fileno()) - tty.setcbreak(sys.stdin.fileno()) - self.chan.settimeout(0.0) - - while True: - try: - r, w, e = select.select([self.chan, sys.stdin], [], []) - except Exception: - pass - - if self.chan in r: - try: - x = self.chan.recv(1024) - if len(x) == 0: - break - sys.stdout.write(x) - sys.stdout.flush() - log_file.write(x) - log_file.flush() - except socket.timeout: - pass - - if sys.stdin in r: - x = os.read(sys.stdin.fileno(), 1) - if len(x) == 0: - break - self.chan.send(x) - - finally: - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) - log_file.write('End time is %s' % datetime.datetime.now()) - log_file.close() - log.is_finished = True - log.handle_finished = False - log.end_time = datetime.datetime.now() - log.save() - - def get_connect_item(self): - """获取连接需要的参数,也就是服务ip, 端口, 用户账号和密码""" - if not self.asset.is_active: - raise ServerError('该主机被禁用 Host %s is not active.' % self.ip) - - if not self.user.is_active: - raise ServerError('该用户被禁用 User %s is not active.' % self.username) - - login_type_dict = { - 'L': self.user.ldap_pwd, - } - - if self.asset.login_type in login_type_dict: - password = CRYPTOR.decrypt(login_type_dict[self.asset.login_type]) - return self.username, password, self.ip, int(self.asset.port) - - elif self.asset.login_type == 'M': - username = self.asset.username - password = CRYPTOR.decrypt(self.asset.password) - return username, password, self.ip, int(self.asset.port) - - else: - raise ServerError('不支持的服务器登录方式 Login type is not in ["L", "M"]') - - def connect(self): - """ - Connect server. - 连接服务器 - """ - username, password, ip, port = self.get_connect_item() - logger.debug("username: %s, password: %s, ip: %s, port: %s" % (username, password, ip, port)) - ps1 = "PS1='[\u@%s \W]\$ '\n" % self.ip - login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % ip - - # 发起ssh连接请求 Make a ssh connection - ssh = paramiko.SSHClient() - ssh.load_system_host_keys() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - try: - ssh.connect(ip, port=port, username=username, password=password, compress=True) - except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException: - raise ServerError('认证错误 Authentication Error.') - except socket.error: - raise ServerError('端口可能不对 Connect SSH Socket Port Error, Please Correct it.') - - # 获取连接的隧道并设置窗口大小 Make a channel and set windows size - global channel - win_size = self.get_win_size() - self.chan = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1]) - try: - signal.signal(signal.SIGWINCH, self.set_win_size) - except: - pass - - # 设置PS1并提示 Set PS1 and msg it - channel.send(ps1) - channel.send(login_msg) - - # Make ssh interactive tunnel - self.posix_shell() - - # Shutdown channel socket - channel.close() - ssh.close() - - def verify_connect(user, option): """鉴定用户是否有该主机权限 或 匹配到的ip是否唯一""" ip_matched = [] diff --git a/jasset/models.py b/jasset/models.py index 58bab367e..9874616b8 100644 --- a/jasset/models.py +++ b/jasset/models.py @@ -28,11 +28,15 @@ class BisGroup(models.Model): def get_asset_info(self, printable=False): assets = self.get_asset() + ip_comment = {} for asset in assets: - if asset.comment: - print '%-15s -- %s' % (asset.ip, asset.comment) + ip_comment[asset.ip] = asset.comment + + for ip in sorted(ip_comment): + if ip_comment[ip]: + print '%-15s -- %s' % (ip, ip_comment[ip]) else: - print '%-15s' % asset.ip + print '%-15s' % ip print '' def get_asset_num(self): diff --git a/jumpserver/api.py b/jumpserver/api.py index 2b133585a..6cff43abd 100644 --- a/jumpserver/api.py +++ b/jumpserver/api.py @@ -1,9 +1,6 @@ # coding: utf-8 - -from django.http import HttpResponseRedirect -import json -import os +import os, sys, time from ConfigParser import ConfigParser import getpass from Crypto.Cipher import AES @@ -14,6 +11,8 @@ import hashlib import datetime import random import subprocess +import paramiko +import struct, fcntl, signal,socket, select, fnmatch from django.core.paginator import Paginator, EmptyPage, InvalidPage from django.http import HttpResponse, Http404 from django.shortcuts import render_to_response @@ -22,10 +21,19 @@ from jasset.models import Asset, BisGroup, IDC from jlog.models import Log from jasset.models import AssetAlias from django.core.exceptions import ObjectDoesNotExist +from django.http import HttpResponseRedirect from django.core.mail import send_mail import json import logging +try: + import termios + import tty +except ImportError: + print '\033[1;31m仅支持类Unix系统 Only unix like supported.\033[0m' + time.sleep(3) + sys.exit() + BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) CONF = ConfigParser() @@ -40,7 +48,7 @@ LDAP_ENABLE = CONF.getint('ldap', 'ldap_enable') SEND_IP = CONF.get('base', 'ip') SEND_PORT = CONF.get('base', 'port') MAIL_FROM = CONF.get('mail', 'email_host_user') - +log_dir = os.path.join(BASE_DIR, 'logs') def set_log(level): log_level_total = {'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARN, 'error': logging.ERROR, @@ -139,6 +147,184 @@ def pages(posts, r): return contact_list, p, contacts, page_range, current_page, show_first, show_end +class Jtty(object): + def __init__(self, user, asset): + self.chan = None + self.username = user.username + self.ip = asset.ip + self.user = user + self.asset = asset + + @staticmethod + def get_win_size(): + """ + This function use to get the size of the windows! + 获得terminal窗口大小 + """ + if 'TIOCGWINSZ' in dir(termios): + TIOCGWINSZ = termios.TIOCGWINSZ + else: + TIOCGWINSZ = 1074295912L + s = struct.pack('HHHH', 0, 0, 0, 0) + x = fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, s) + return struct.unpack('HHHH', x)[0:2] + + def set_win_size(self, sig, data): + """ + This function use to set the window size of the terminal! + 设置terminal窗口大小 + """ + try: + win_size = self.get_win_size() + self.chan.resize_pty(height=win_size[0], width=win_size[1]) + except Exception: + pass + + def log_record(self): + """ + Logging user command and output. + 记录用户的日志 + """ + tty_log_dir = os.path.join(log_dir, 'tty') + timestamp_start = int(time.time()) + date_start = time.strftime('%Y%m%d', time.localtime(timestamp_start)) + time_start = time.strftime('%H%M%S', time.localtime(timestamp_start)) + log_filename = '%s_%s_%s.log' % (self.username, self.ip, time_start) + today_connect_log_dir = os.path.join(tty_log_dir, date_start) + log_file_path = os.path.join(today_connect_log_dir, log_filename) + dept_name = self.user.dept.name + + pid = os.getpid() + pts = os.popen("ps axu | grep %s | grep -v grep | awk '{ print $7 }'" % pid).read().strip() + ip_list = os.popen("who | grep %s | awk '{ print $5 }'" % pts).read().strip('()\n') + + try: + is_dir(today_connect_log_dir) + except OSError: + raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, tty_log_dir)) + + try: + log_file = open(log_file_path, 'a') + except IOError: + raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir) + + log = Log(user=self.username, host=self.ip, remote_ip=ip_list, dept_name=dept_name, + log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid) + log_file.write('Start time is %s\n' % datetime.datetime.now()) + log.save() + return log_file, log + + def posix_shell(self): + """ + Use paramiko channel connect server interactive. + 使用paramiko模块的channel,连接后端,进入交互式 + """ + log_file, log = self.log_record() + old_tty = termios.tcgetattr(sys.stdin) + try: + tty.setraw(sys.stdin.fileno()) + tty.setcbreak(sys.stdin.fileno()) + self.chan.settimeout(0.0) + + while True: + try: + r, w, e = select.select([self.chan, sys.stdin], [], []) + except Exception: + pass + + if self.chan in r: + try: + x = self.chan.recv(1024) + if len(x) == 0: + break + sys.stdout.write(x) + sys.stdout.flush() + log_file.write(x) + log_file.flush() + except socket.timeout: + pass + + if sys.stdin in r: + x = os.read(sys.stdin.fileno(), 1) + if len(x) == 0: + break + self.chan.send(x) + + finally: + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) + log_file.write('End time is %s' % datetime.datetime.now()) + log_file.close() + log.is_finished = True + log.handle_finished = False + log.end_time = datetime.datetime.now() + log.save() + + def get_connect_item(self): + """获取连接需要的参数,也就是服务ip, 端口, 用户账号和密码""" + if not self.asset.is_active: + raise ServerError('该主机被禁用 Host %s is not active.' % self.ip) + + if not self.user.is_active: + raise ServerError('该用户被禁用 User %s is not active.' % self.username) + + login_type_dict = { + 'L': self.user.ldap_pwd, + } + + if self.asset.login_type in login_type_dict: + password = CRYPTOR.decrypt(login_type_dict[self.asset.login_type]) + return self.username, password, self.ip, int(self.asset.port) + + elif self.asset.login_type == 'M': + username = self.asset.username + password = CRYPTOR.decrypt(self.asset.password) + return username, password, self.ip, int(self.asset.port) + + else: + raise ServerError('不支持的服务器登录方式 Login type is not in ["L", "M"]') + + def connect(self): + """ + Connect server. + 连接服务器 + """ + username, password, ip, port = self.get_connect_item() + logger.debug("username: %s, password: %s, ip: %s, port: %s" % (username, password, ip, port)) + ps1 = "PS1='[\u@%s \W]\$ '\n" % self.ip + login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % ip + + # 发起ssh连接请求 Make a ssh connection + ssh = paramiko.SSHClient() + ssh.load_system_host_keys() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + ssh.connect(ip, port=port, username=username, password=password, compress=True) + except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException: + raise ServerError('认证错误 Authentication Error.') + except socket.error: + raise ServerError('端口可能不对 Connect SSH Socket Port Error, Please Correct it.') + + # 获取连接的隧道并设置窗口大小 Make a channel and set windows size + global channel + win_size = self.get_win_size() + self.chan = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1]) + try: + signal.signal(signal.SIGWINCH, self.set_win_size) + except: + pass + + # 设置PS1并提示 Set PS1 and msg it + channel.send(ps1) + channel.send(login_msg) + + # Make ssh interactive tunnel + self.posix_shell() + + # Shutdown channel socket + channel.close() + ssh.close() + + class PyCrypt(object): """ This class used to encrypt and decrypt password.