You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
jumpserver/connect.py

311 lines
8.8 KiB

10 years ago
#coding: utf-8
import socket
import sys
import os
import select
import time
import paramiko
import struct
import fcntl
import signal
import textwrap
import django
10 years ago
import getpass
10 years ago
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
from ConfigParser import ConfigParser
from django.core.exceptions import ObjectDoesNotExist
10 years ago
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
10 years ago
django.setup()
10 years ago
from juser.models import User
from jasset.models import Asset
10 years ago
from jlog.models import Log
10 years ago
try:
import termios
import tty
except ImportError:
print '\033[1;31mOnly postfix supported.\033[0m'
sys.exit()
CURRENT_DIR = os.path.abspath('.')
CONF = ConfigParser()
CONF.read(os.path.join(CURRENT_DIR, 'jumpserver.conf'))
10 years ago
LOG_DIR = os.path.join(CURRENT_DIR, 'logs')
KEY = CONF.get('web', 'key')
LOGIN_NAME = getpass.getuser()
#LOGIN_NAME = os.getlogin()
10 years ago
def green_print(string):
print '\033[1;32m%s\033[0m' % string
def red_print(string):
print '\033[1;31m%s\033[0m' % string
def alert_print(string):
red_print('AlertError: %s' % string)
time.sleep(2)
sys.exit()
class PyCrypt(object):
"""It's used to encrypt and decrypt password."""
def __init__(self, key):
self.key = key
self.mode = AES.MODE_CBC
def encrypt(self, text):
cryptor = AES.new(self.key, self.mode, b'0000000000000000')
length = 16
count = len(text)
if count < length:
add = (length - count)
text += ('\0' * add)
elif count > length:
add = (length - (count % length))
text += ('\0' * add)
ciphertext = cryptor.encrypt(text)
return b2a_hex(ciphertext)
def decrypt(self, text):
cryptor = AES.new(self.key, self.mode, b'0000000000000000')
plain_text = cryptor.decrypt(a2b_hex(text))
return plain_text.rstrip('\0')
def get_win_size():
"""This function use to get the size of the windows!"""
if 'TIOCGWINSZ' in dir(termios):
TIOCGWINSZ = termios.TIOCGWINSZ
else:
TIOCGWINSZ = 1074295912L # Assume
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(sig, data):
"""This function use to set the window size of the terminal!"""
try:
win_size = get_win_size()
channel.resize_pty(height=win_size[0], width=win_size[1])
except:
pass
10 years ago
def posix_shell(chan, username, host):
10 years ago
"""
Use paramiko channel connect server and logging.
"""
connect_log_dir = os.path.join(LOG_DIR, 'connect')
10 years ago
timestamp_start = int(time.time())
today = time.strftime('%Y%m%d', time.localtime(timestamp_start))
date_now = time.strftime('%Y%m%d%H%M%S', time.localtime(timestamp_start))
10 years ago
today_connect_log_dir = os.path.join(connect_log_dir, today)
10 years ago
log_filename = '%s_%s_%s.log' % (username, host, date_now)
10 years ago
log_file_path = os.path.join(today_connect_log_dir, log_filename)
10 years ago
user = User.objects.get(username=username)
asset = Asset.objects.get(ip=host)
pid = os.getpid()
10 years ago
if not os.path.isdir(today_connect_log_dir):
try:
os.makedirs(today_connect_log_dir)
10 years ago
os.chmod(today_connect_log_dir, 0777)
10 years ago
except OSError:
alert_print('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, connect_log_dir))
try:
10 years ago
log_file = open(log_file_path, 'a')
10 years ago
except IOError:
alert_print('Create logfile failed, Please modify %s permission.' % today_connect_log_dir)
10 years ago
log = Log(user=user, asset=asset, log_path=log_file_path, start_time=timestamp_start, pid=pid)
10 years ago
log.save()
10 years ago
old_tty = termios.tcgetattr(sys.stdin)
try:
tty.setraw(sys.stdin.fileno())
tty.setcbreak(sys.stdin.fileno())
chan.settimeout(0.0)
while True:
try:
r, w, e = select.select([chan, sys.stdin], [], [])
except:
pass
if chan in r:
try:
x = chan.recv(1024)
if len(x) == 0:
break
sys.stdout.write(x)
sys.stdout.flush()
10 years ago
log_file.write(x)
log_file.flush()
10 years ago
except socket.timeout:
pass
if sys.stdin in r:
x = os.read(sys.stdin.fileno(), 1)
if len(x) == 0:
break
chan.send(x)
finally:
10 years ago
timestamp_end = time.time()
10 years ago
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
10 years ago
log_file.close()
log.is_finished = True
log.end_time = timestamp_end
log.save()
10 years ago
10 years ago
def get_user_host(username):
10 years ago
hosts_attr = {}
10 years ago
try:
user = User.objects.get(username=username)
10 years ago
except ObjectDoesNotExist:
10 years ago
return {'Error': ['0', "Username \033[1;31m%s\033[0m doesn't exist on Jumpserver." % username]}, ['Error']
10 years ago
else:
perm_all = user.permission_set.all()
for perm in perm_all:
10 years ago
hosts_attr[perm.asset.ip] = [perm.asset.id, perm.asset.comment]
hosts = hosts_attr.keys()
10 years ago
hosts.sort()
10 years ago
return hosts_attr, hosts
10 years ago
10 years ago
def get_connect_item(username, ip):
cryptor = PyCrypt(KEY)
10 years ago
try:
asset = Asset.objects.get(ip=ip)
port = asset.port
except ObjectDoesNotExist:
red_print("Host %s isn't exist." % ip)
return
10 years ago
10 years ago
if asset.ldap_enable:
10 years ago
user = User.objects.get(username=username)
ldap_pwd = cryptor.decrypt(user.ldap_pwd)
10 years ago
return username, ldap_pwd, ip, port
else:
perms = asset.permission_set.all()
perm = perms[0]
10 years ago
if perm.role == 'SU':
return asset.username_super, cryptor.decrypt(asset.password_super), ip, port
10 years ago
else:
return asset.username_common, cryptor.decrypt(asset.password_common), ip, port
10 years ago
def verify_connect(username, part_ip):
ip_matched = []
10 years ago
hosts_mix, hosts = get_user_host(username)
10 years ago
for ip in hosts:
if part_ip in ip:
ip_matched.append(ip)
if len(ip_matched) > 1:
for ip in ip_matched:
10 years ago
print '[%s] %s -- %s' % (hosts_mix[ip][0], ip, hosts_mix[ip][1])
10 years ago
elif len(ip_matched) < 1:
red_print('No Permission or No host.')
else:
try:
username, password, host, port = get_connect_item(username, ip_matched[0])
except (ObjectDoesNotExist, IndexError):
red_print('Get get_connect_item Error.')
else:
connect(username, password, host, port, LOGIN_NAME)
10 years ago
10 years ago
def print_prompt():
10 years ago
msg = """\033[1;32m### Welcome Use JumpServer To Login. ### \033[0m
10 years ago
1) Type \033[32mIP ADDRESS\033[0m To Login.
2) Type \033[32mP/p\033[0m To Print The Servers You Available.
3) Type \033[32mE/e\033[0m To Execute Command On Several Servers.
4) Type \033[32mQ/q\033[0m To Quit.
"""
print textwrap.dedent(msg)
def print_user_host(username):
10 years ago
hosts_attr, hosts = get_user_host(username)
10 years ago
for ip in hosts:
10 years ago
print '[%s] %s -- %s' % (hosts_attr[ip][0], ip, hosts_attr[ip][1])
10 years ago
def connect(username, password, host, port, login_name):
10 years ago
"""
Connect server.
"""
ps1 = "PS1='[\u@%s \W]\$ '\n" % host
login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % host
# Make a ssh connection
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(host, port=port, username=username, password=password, compress=True)
except paramiko.ssh_exception.AuthenticationException:
alert_print('Host Password Error, Please Correct it.')
except socket.error:
alert_print('Connect SSH Socket Port Error, Please Correct it.')
# Make a channel and set windows size
global channel
channel = ssh.invoke_shell()
win_size = get_win_size()
channel.resize_pty(height=win_size[0], width=win_size[1])
try:
signal.signal(signal.SIGWINCH, set_win_size)
except:
pass
# Set PS1 and msg it
channel.send(ps1)
channel.send(login_msg)
# Make ssh interactive tunnel
posix_shell(channel, login_name, host)
10 years ago
# Shutdown channel socket
channel.close()
ssh.close()
if __name__ == '__main__':
print_prompt()
try:
while True:
try:
option = raw_input("\033[1;32mOpt or IP>:\033[0m ")
except EOFError:
10 years ago
print
10 years ago
continue
if option in ['P', 'p']:
print_user_host(LOGIN_NAME)
10 years ago
continue
elif option in ['E', 'e']:
pass
elif option in ['Q', 'q']:
sys.exit()
else:
verify_connect(LOGIN_NAME, option)
10 years ago
except IndexError:
pass