mirror of https://github.com/jumpserver/jumpserver
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.
369 lines
11 KiB
369 lines
11 KiB
#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
|
|
import getpass
|
|
from Crypto.Cipher import AES
|
|
from binascii import b2a_hex, a2b_hex
|
|
from ConfigParser import ConfigParser
|
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
|
|
django.setup()
|
|
|
|
from juser.models import User
|
|
from jasset.models import Asset
|
|
from jlog.models import Log
|
|
|
|
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'))
|
|
LOG_DIR = os.path.join(CURRENT_DIR, 'logs')
|
|
SSH_KEY_DIR = os.path.join(CURRENT_DIR, 'keys')
|
|
SERVER_KEY_DIR = os.path.join(SSH_KEY_DIR, 'server')
|
|
KEY = CONF.get('web', 'key')
|
|
LOGIN_NAME = getpass.getuser()
|
|
#LOGIN_NAME = os.getlogin()
|
|
|
|
|
|
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 ServerError(Exception):
|
|
def __init__(self, error):
|
|
self.error = error
|
|
|
|
def __str__(self):
|
|
return self.error
|
|
|
|
__repr__ = __str__
|
|
|
|
|
|
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
|
|
|
|
|
|
def posix_shell(chan, username, host):
|
|
"""
|
|
Use paramiko channel connect server and logging.
|
|
"""
|
|
connect_log_dir = os.path.join(LOG_DIR, 'connect')
|
|
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))
|
|
|
|
today_connect_log_dir = os.path.join(connect_log_dir, today)
|
|
log_filename = '%s_%s_%s.log' % (username, host, date_now)
|
|
log_file_path = os.path.join(today_connect_log_dir, log_filename)
|
|
|
|
try:
|
|
user = User.objects.get(username=username)
|
|
asset = Asset.objects.get(ip=host)
|
|
except ObjectDoesNotExist:
|
|
raise ServerError('user %s or asset %s does not exist.' % (username, host))
|
|
|
|
pid = os.getpid()
|
|
if not os.path.isdir(today_connect_log_dir):
|
|
try:
|
|
os.makedirs(today_connect_log_dir)
|
|
os.chmod(today_connect_log_dir, 0777)
|
|
except OSError:
|
|
raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, connect_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=user, asset=asset, log_path=log_file_path, start_time=timestamp_start, pid=pid)
|
|
log.save()
|
|
|
|
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()
|
|
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
|
|
chan.send(x)
|
|
|
|
finally:
|
|
timestamp_end = time.time()
|
|
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
|
|
log_file.close()
|
|
log.is_finished = True
|
|
log.end_time = timestamp_end
|
|
log.save()
|
|
|
|
|
|
def get_user_host(username):
|
|
hosts_attr = {}
|
|
try:
|
|
user = User.objects.get(username=username)
|
|
except ObjectDoesNotExist:
|
|
raise ServerError("Username \033[1;31m%s\033[0m doesn't exist on Jumpserver." % username)
|
|
else:
|
|
perm_all = user.permission_set.all()
|
|
for perm in perm_all:
|
|
hosts_attr[perm.asset.ip] = [perm.asset.id, perm.asset.comment]
|
|
hosts = hosts_attr.keys()
|
|
hosts.sort()
|
|
return hosts_attr, hosts
|
|
|
|
|
|
def get_connect_item(username, ip):
|
|
cryptor = PyCrypt(KEY)
|
|
|
|
try:
|
|
asset = Asset.objects.get(ip=ip)
|
|
port = asset.port
|
|
except ObjectDoesNotExist:
|
|
raise ServerError("Host %s does not exist." % ip)
|
|
|
|
try:
|
|
user = User.objects.get(username=username)
|
|
except ObjectDoesNotExist:
|
|
raise ServerError('User %s does not exist.' % username)
|
|
|
|
if asset.login_type == 'L':
|
|
try:
|
|
ldap_pwd = cryptor.decrypt(user.ldap_pwd)
|
|
except TypeError:
|
|
raise ServerError('Decrypt %s ldap password error.' % username)
|
|
return 'L', username, ldap_pwd, ip, port
|
|
elif asset.login_type == 'S':
|
|
try:
|
|
ssh_key_pwd = cryptor.decrypt(user.ssh_key_pwd2)
|
|
except TypeError:
|
|
raise ServerError('Decrypt %s ssh key password error.' % username)
|
|
return 'S', username, ssh_key_pwd, ip, port
|
|
elif asset.login_type == 'P':
|
|
try:
|
|
ssh_pwd = cryptor.decrypt(user.ssh_pwd)
|
|
except TypeError:
|
|
raise ServerError('Decrypt %s ssh password error.' % username)
|
|
return 'P', username, ssh_pwd, ip, port
|
|
elif asset.login_type == 'M':
|
|
perms = asset.permission_set.filter(user=user)
|
|
try:
|
|
perm = perms[0]
|
|
except IndexError:
|
|
raise ServerError('Permission %s to %s does not exist.' % (username, ip))
|
|
|
|
if perm.role == 'SU':
|
|
username_super = asset.username_super
|
|
try:
|
|
password_super = cryptor.decrypt(asset.password_super)
|
|
except TypeError:
|
|
raise ServerError('Decrypt %s map to %s password in %s error.' % (username, username_super, ip))
|
|
return 'M', username_super, password_super, ip, port
|
|
|
|
elif perm.role == 'CU':
|
|
username_common = asset.username_common
|
|
try:
|
|
password_common = asset.password_common
|
|
except TypeError:
|
|
raise ServerError('Decrypt %s map to %s password in %s error.' % (username, username_common, ip))
|
|
return username_common, password_common, ip, port
|
|
|
|
else:
|
|
raise ServerError('Perm in %s for %s map role is not in ["SU", "CU"].' % (ip, username))
|
|
else:
|
|
raise ServerError('Login type is not in ["L", "S", "P", "M"]')
|
|
|
|
|
|
def verify_connect(username, part_ip):
|
|
ip_matched = []
|
|
hosts_mix, hosts = get_user_host(username)
|
|
for ip in hosts:
|
|
if part_ip in ip:
|
|
ip_matched.append(ip)
|
|
|
|
if len(ip_matched) > 1:
|
|
for ip in ip_matched:
|
|
print '[%s] %s -- %s' % (hosts_mix[ip][0], ip, hosts_mix[ip][1])
|
|
elif len(ip_matched) < 1:
|
|
red_print('No Permission or No host.')
|
|
else:
|
|
login_type, username, password, host, port = get_connect_item(username, ip_matched[0])
|
|
connect(username, password, host, port, LOGIN_NAME, login_type=login_type)
|
|
|
|
|
|
def print_prompt():
|
|
msg = """\033[1;32m### Welcome Use JumpServer To Login. ### \033[0m
|
|
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):
|
|
hosts_attr, hosts = get_user_host(username)
|
|
for ip in hosts:
|
|
print '[%s] %s -- %s' % (hosts_attr[ip][0], ip, hosts_attr[ip][1])
|
|
|
|
|
|
def connect(username, password, host, port, login_name, login_type='L'):
|
|
"""
|
|
Connect server.
|
|
"""
|
|
ps1 = "PS1='[\u@%s \W]\$ '\n" % host
|
|
login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % host
|
|
user_key_file = os.path.join(SERVER_KEY_DIR, username)
|
|
|
|
if os.path.isfile(user_key_file):
|
|
key_filename = user_key_file
|
|
else:
|
|
key_filename = None
|
|
|
|
# Make a ssh connection
|
|
ssh = paramiko.SSHClient()
|
|
ssh.load_system_host_keys()
|
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
try:
|
|
if login_type == 'L':
|
|
ssh.connect(host, port=port, username=username, password=password, key_filename=key_filename, compress=True)
|
|
else:
|
|
ssh.connect(host, port=port, username=username, password=password, compress=True)
|
|
except paramiko.ssh_exception.AuthenticationException:
|
|
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 = get_win_size()
|
|
channel = ssh.invoke_shell(height=win_size[0], width=win_size[1])
|
|
#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)
|
|
|
|
# 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:
|
|
print
|
|
continue
|
|
if option in ['P', 'p']:
|
|
print_user_host(LOGIN_NAME)
|
|
continue
|
|
elif option in ['E', 'e']:
|
|
pass
|
|
elif option in ['Q', 'q']:
|
|
sys.exit()
|
|
else:
|
|
try:
|
|
verify_connect(LOGIN_NAME, option)
|
|
except ServerError, e:
|
|
red_print(e)
|
|
except IndexError:
|
|
pass |