jumpserver/connect.py

428 lines
13 KiB
Python
Raw Normal View History

2014-12-30 14:48:18 +00:00
# coding: utf-8
2014-12-23 16:21:47 +00:00
import socket
import sys
import os
import select
import time
import paramiko
import struct
import fcntl
import signal
import textwrap
import django
2014-12-24 14:56:29 +00:00
import getpass
2014-12-30 14:48:18 +00:00
import fnmatch
2015-01-03 01:52:52 +00:00
import readline
2014-12-30 14:48:18 +00:00
from multiprocessing import Pool
2014-12-23 16:21:47 +00:00
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
2014-12-27 08:52:31 +00:00
from ConfigParser import ConfigParser
2014-12-26 13:55:56 +00:00
from django.core.exceptions import ObjectDoesNotExist
2014-12-30 14:48:18 +00:00
2014-12-24 14:20:38 +00:00
os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings'
2014-12-23 16:21:47 +00:00
django.setup()
2014-12-25 16:11:13 +00:00
from juser.models import User
from jasset.models import Asset
2014-12-26 15:58:11 +00:00
from jlog.models import Log
2014-12-23 16:21:47 +00:00
try:
import termios
import tty
except ImportError:
print '\033[1;31mOnly postfix supported.\033[0m'
2014-12-31 14:58:37 +00:00
time.sleep(3)
2014-12-23 16:21:47 +00:00
sys.exit()
2015-01-12 15:52:41 +00:00
BASE_DIR = os.path.dirname(__file__)
2014-12-27 08:52:31 +00:00
CONF = ConfigParser()
2015-01-12 15:52:41 +00:00
CONF.read(os.path.join(BASE_DIR, 'jumpserver.conf'))
LOG_DIR = os.path.join(BASE_DIR, 'logs')
2014-12-31 14:58:37 +00:00
# Web generate user ssh_key dir.
2015-01-12 15:52:41 +00:00
SSH_KEY_DIR = os.path.join(BASE_DIR, 'keys')
2014-12-31 14:58:37 +00:00
# User upload the server key to this dir.
2014-12-29 15:50:56 +00:00
SERVER_KEY_DIR = os.path.join(SSH_KEY_DIR, 'server')
2014-12-31 14:58:37 +00:00
# The key of decryptor.
2014-12-27 08:52:31 +00:00
KEY = CONF.get('web', 'key')
2014-12-31 14:58:37 +00:00
# Login user.
LOGIN_NAME = getpass.getuser()
#LOGIN_NAME = os.getlogin()
2014-12-31 14:58:37 +00:00
USER_KEY_FILE = os.path.join(SERVER_KEY_DIR, LOGIN_NAME)
2014-12-23 16:21:47 +00:00
2014-12-31 14:58:37 +00:00
if not os.path.isfile(USER_KEY_FILE):
USER_KEY_FILE = None
2014-12-23 16:21:47 +00:00
2014-12-30 14:48:18 +00:00
2014-12-31 14:58:37 +00:00
def color_print(msg, color='blue'):
"""Print colorful string."""
color_msg = {'blue': '\033[1;36m%s\033[0m',
'green': '\033[1;32m%s\033[0m',
'red': '\033[1;31m%s\033[0m'}
2014-12-30 14:48:18 +00:00
2014-12-31 14:58:37 +00:00
print color_msg.get(color, 'blue') % msg
2014-12-23 16:21:47 +00:00
2014-12-31 14:58:37 +00:00
def color_print_exit(msg, color='red'):
"""Print colorful string and exit."""
color_print(msg, color=color)
2014-12-23 16:21:47 +00:00
time.sleep(2)
sys.exit()
class ServerError(Exception):
2014-12-31 14:58:37 +00:00
pass
2014-12-23 16:21:47 +00:00
class PyCrypt(object):
2014-12-31 14:58:37 +00:00
"""This class used to encrypt and decrypt password."""
2014-12-30 14:48:18 +00:00
2014-12-23 16:21:47 +00:00
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
2014-12-31 14:58:37 +00:00
try:
count = len(text)
except TypeError:
raise ServerError('Encrypt password error, TYpe error.')
add = (length - (count % length))
text += ('\0' * add)
2014-12-23 16:21:47 +00:00
ciphertext = cryptor.encrypt(text)
return b2a_hex(ciphertext)
def decrypt(self, text):
cryptor = AES.new(self.key, self.mode, b'0000000000000000')
2014-12-31 14:58:37 +00:00
try:
plain_text = cryptor.decrypt(a2b_hex(text))
except TypeError:
raise ServerError('Decrypt password error, TYpe error.')
2014-12-23 16:21:47 +00:00
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
2014-12-31 14:58:37 +00:00
def get_object(model, **kwargs):
try:
2015-01-01 00:44:46 +00:00
the_object = model.objects.get(**kwargs)
2014-12-31 14:58:37 +00:00
except ObjectDoesNotExist:
raise ServerError('Object get %s failed.' % str(kwargs.values()))
return the_object
def log_record(username, host):
"""Logging user command and output."""
2014-12-23 16:21:47 +00:00
connect_log_dir = os.path.join(LOG_DIR, 'connect')
2014-12-26 15:58:11 +00:00
timestamp_start = int(time.time())
today = time.strftime('%Y%m%d', time.localtime(timestamp_start))
2014-12-31 14:58:37 +00:00
time_now = time.strftime('%H%M%S', time.localtime(timestamp_start))
2014-12-23 16:21:47 +00:00
today_connect_log_dir = os.path.join(connect_log_dir, today)
2014-12-31 14:58:37 +00:00
log_filename = '%s_%s_%s.log' % (username, host, time_now)
2014-12-23 16:21:47 +00:00
log_file_path = os.path.join(today_connect_log_dir, log_filename)
2014-12-31 14:58:37 +00:00
pid = os.getpid()
2014-12-23 16:21:47 +00:00
2014-12-31 14:58:37 +00:00
user = get_object(User, username=username)
asset = get_object(Asset, ip=host)
2014-12-23 16:21:47 +00:00
if not os.path.isdir(today_connect_log_dir):
try:
os.makedirs(today_connect_log_dir)
2014-12-25 16:11:13 +00:00
os.chmod(today_connect_log_dir, 0777)
2014-12-23 16:21:47 +00:00
except OSError:
raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, connect_log_dir))
2014-12-23 16:21:47 +00:00
try:
2014-12-26 15:58:11 +00:00
log_file = open(log_file_path, 'a')
2014-12-23 16:21:47 +00:00
except IOError:
raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir)
2014-12-23 16:21:47 +00:00
2014-12-26 15:59:12 +00:00
log = Log(user=user, asset=asset, log_path=log_file_path, start_time=timestamp_start, pid=pid)
2014-12-26 15:58:11 +00:00
log.save()
2014-12-31 14:58:37 +00:00
return log_file, log
2014-12-26 15:58:11 +00:00
2014-12-31 14:58:37 +00:00
def posix_shell(chan, username, host):
"""
Use paramiko channel connect server interactive.
"""
log_file, log = log_record(username, host)
2014-12-23 16:21:47 +00:00
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()
2014-12-26 15:58:11 +00:00
log_file.write(x)
log_file.flush()
2014-12-23 16:21:47 +00:00
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:
2014-12-26 15:58:11 +00:00
timestamp_end = time.time()
2014-12-23 16:21:47 +00:00
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
2014-12-26 15:58:11 +00:00
log_file.close()
log.is_finished = True
log.end_time = timestamp_end
log.save()
2014-12-23 16:21:47 +00:00
2014-12-24 14:29:36 +00:00
def get_user_host(username):
2014-12-31 14:58:37 +00:00
"""Get the hosts of under the user control."""
2014-12-26 13:39:05 +00:00
hosts_attr = {}
2014-12-23 16:21:47 +00:00
try:
user = User.objects.get(username=username)
2014-12-24 15:17:51 +00:00
except ObjectDoesNotExist:
raise ServerError("Username \033[1;31m%s\033[0m doesn't exist on Jumpserver." % username)
2014-12-23 16:21:47 +00:00
else:
perm_all = user.permission_set.all()
for perm in perm_all:
2014-12-26 13:39:05 +00:00
hosts_attr[perm.asset.ip] = [perm.asset.id, perm.asset.comment]
2014-12-31 14:58:37 +00:00
return hosts_attr
2014-12-23 16:21:47 +00:00
2014-12-25 15:49:08 +00:00
def get_connect_item(username, ip):
2014-12-27 08:52:31 +00:00
cryptor = PyCrypt(KEY)
2015-01-01 00:44:46 +00:00
asset = get_object(Asset, ip=ip)
2014-12-31 14:58:37 +00:00
port = asset.port
2014-12-24 15:40:52 +00:00
2014-12-30 14:52:39 +00:00
if not asset.is_active:
raise ServerError('Host %s is not active.' % ip)
2014-12-31 14:58:37 +00:00
user = get_object(User, username=username)
2014-12-30 14:52:39 +00:00
if not user.is_active:
raise ServerError('User %s is not active.' % username)
2014-12-31 14:58:37 +00:00
login_type_dict = {
'L': user.ldap_pwd,
'S': user.ssh_key_pwd2,
'P': user.ssh_pwd,
}
if asset.login_type in login_type_dict:
password = cryptor.decrypt(login_type_dict[asset.login_type])
return username, password, ip, port
elif asset.login_type == 'M':
perms = asset.permission_set.filter(user=user)
2014-12-31 14:58:37 +00:00
if perms:
perm = perms[0]
2014-12-31 14:58:37 +00:00
else:
raise ServerError('Permission %s to %s does not exist.' % (username, ip))
2014-12-25 15:49:08 +00:00
2014-12-27 16:14:10 +00:00
if perm.role == 'SU':
username_super = asset.username_super
2014-12-31 14:58:37 +00:00
password_super = cryptor.decrypt(asset.password_super)
return username_super, password_super, ip, port
elif perm.role == 'CU':
username_common = asset.username_common
2014-12-31 14:58:37 +00:00
password_common = asset.password_common
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"]')
2014-12-25 15:49:08 +00:00
def verify_connect(username, part_ip):
2014-12-31 14:58:37 +00:00
hosts_attr = get_user_host(username)
hosts = hosts_attr.keys()
ip_matched = [ip for ip in hosts if part_ip in ip]
2014-12-25 15:49:08 +00:00
if len(ip_matched) > 1:
for ip in ip_matched:
2014-12-31 14:58:37 +00:00
print '[%s] %s -- %s' % (hosts_attr[ip][0], ip, hosts_attr[ip][1])
2014-12-25 15:49:08 +00:00
elif len(ip_matched) < 1:
2014-12-31 14:58:37 +00:00
color_print('No Permission or No host.', 'red')
2014-12-25 15:49:08 +00:00
else:
2014-12-31 15:02:03 +00:00
username, password, host, port = get_connect_item(username, ip_matched[0])
2014-12-31 14:58:37 +00:00
connect(username, password, host, port, LOGIN_NAME)
2014-12-24 15:40:52 +00:00
2014-12-23 16:21:47 +00:00
def print_prompt():
2014-12-24 14:56:29 +00:00
msg = """\033[1;32m### Welcome Use JumpServer To Login. ### \033[0m
2014-12-23 16:21:47 +00:00
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):
2014-12-31 14:58:37 +00:00
hosts_attr = get_user_host(username)
hosts = hosts_attr.keys()
hosts.sort()
2014-12-25 15:49:08 +00:00
for ip in hosts:
2014-12-26 13:39:05 +00:00
print '[%s] %s -- %s' % (hosts_attr[ip][0], ip, hosts_attr[ip][1])
2014-12-23 16:21:47 +00:00
2014-12-31 14:58:37 +00:00
def connect(username, password, host, port, login_name):
2014-12-23 16:21:47 +00:00
"""
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:
2014-12-31 14:58:37 +00:00
ssh.connect(host, port=port, username=username, password=password, key_filename=USER_KEY_FILE, compress=True)
2014-12-30 15:51:00 +00:00
except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException:
raise ServerError('Authentication Error.')
2014-12-23 16:21:47 +00:00
except socket.error:
raise ServerError('Connect SSH Socket Port Error, Please Correct it.')
2014-12-23 16:21:47 +00:00
# Make a channel and set windows size
global channel
win_size = get_win_size()
2014-12-29 15:50:56 +00:00
channel = ssh.invoke_shell(height=win_size[0], width=win_size[1])
#channel.resize_pty(height=win_size[0], width=win_size[1])
2014-12-23 16:21:47 +00:00
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)
2014-12-23 16:21:47 +00:00
# Shutdown channel socket
channel.close()
ssh.close()
2014-12-30 16:11:12 +00:00
def remote_exec_cmd(ip, port, username, password, cmd):
2014-12-30 14:48:18 +00:00
try:
2015-01-04 03:12:38 +00:00
time.sleep(5)
2014-12-30 14:48:18 +00:00
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
2014-12-31 14:58:37 +00:00
ssh.connect(ip, port, username, password, key_filename=USER_KEY_FILE, timeout=5)
2014-12-30 14:48:18 +00:00
stdin, stdout, stderr = ssh.exec_command("bash -l -c '%s'" % cmd)
out = stdout.readlines()
err = stderr.readlines()
2015-01-04 03:12:38 +00:00
color_print('%s:' %ip, 'blue')
2014-12-30 14:48:18 +00:00
for i in out:
2014-12-31 14:58:37 +00:00
color_print(" " * 4 + i.strip(), 'green')
2014-12-30 14:48:18 +00:00
for j in err:
2014-12-31 14:58:37 +00:00
color_print(" " * 4 + j.strip(), 'red')
2014-12-30 14:48:18 +00:00
ssh.close()
except Exception as e:
2014-12-31 14:58:37 +00:00
color_print(ip + ':', 'blue')
color_print(str(e), 'red')
2014-12-30 14:48:18 +00:00
2014-12-30 16:11:12 +00:00
def multi_remote_exec_cmd(hosts, username, cmd):
2015-01-04 03:12:38 +00:00
pool = Pool(processes=5)
2014-12-30 14:48:18 +00:00
for host in hosts:
2014-12-31 14:58:37 +00:00
username, password, ip, port = get_connect_item(username, host)
2014-12-30 16:11:12 +00:00
pool.apply_async(remote_exec_cmd, (ip, port, username, password, cmd))
2014-12-30 14:48:18 +00:00
pool.close()
pool.join()
def exec_cmd_servers(username):
hosts = []
2014-12-31 14:58:37 +00:00
color_print("Input the Host IP(s),Separated by Commas, q/Q to Quit.\n \
You can choose in the following IP(s), Use Linux / Unix glob.", 'green')
2014-12-30 14:48:18 +00:00
print_user_host(LOGIN_NAME)
while True:
inputs = raw_input('\033[1;32mip(s)>: \033[0m')
if inputs in ['q', 'Q']:
break
2015-01-01 01:15:01 +00:00
get_hosts = get_user_host(username).keys()
2014-12-30 14:48:18 +00:00
for host in get_hosts:
if fnmatch.fnmatch(host, inputs):
hosts.append(host.strip())
if len(hosts) == 0:
2014-12-31 14:58:37 +00:00
color_print("Check again, Not matched any ip!", 'red')
2014-12-30 14:48:18 +00:00
continue
else:
print "You matched ip: %s" % hosts
2014-12-31 14:58:37 +00:00
color_print("Input the Command , The command will be Execute on servers, q/Q to quit.", 'green')
2014-12-30 14:48:18 +00:00
while True:
cmd = raw_input('\033[1;32mCmd(s): \033[0m')
if cmd in ['q', 'Q']:
break
exec_log_dir = os.path.join(LOG_DIR, 'exec_cmds')
if not os.path.isdir(exec_log_dir):
os.mkdir(exec_log_dir)
os.chmod(exec_log_dir, 0777)
filename = "%s/%s.log" % (exec_log_dir, time.strftime('%Y%m%d'))
f = open(filename, 'a')
f.write("DateTime: %s User: %s Host: %s Cmds: %s\n" %
(time.strftime('%Y/%m/%d %H:%M:%S'), username, hosts, cmd))
2014-12-30 16:11:12 +00:00
multi_remote_exec_cmd(hosts, username, cmd)
2014-12-30 14:48:18 +00:00
2014-12-23 16:21:47 +00:00
if __name__ == '__main__':
print_prompt()
try:
while True:
try:
option = raw_input("\033[1;32mOpt or IP>:\033[0m ")
except EOFError:
2014-12-24 15:40:52 +00:00
print
2014-12-23 16:21:47 +00:00
continue
if option in ['P', 'p']:
print_user_host(LOGIN_NAME)
2014-12-23 16:21:47 +00:00
continue
elif option in ['E', 'e']:
2014-12-30 14:48:18 +00:00
exec_cmd_servers(LOGIN_NAME)
2014-12-23 16:21:47 +00:00
elif option in ['Q', 'q']:
sys.exit()
else:
try:
verify_connect(LOGIN_NAME, option)
except ServerError, e:
2014-12-31 14:58:37 +00:00
color_print(e, 'red')
2014-12-23 16:21:47 +00:00
except IndexError:
pass