Unverified Commit 08b15983 authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #29 from jumpserver/dev

Merge to master
parents 9698e4f8 c1288ecd
...@@ -150,10 +150,10 @@ class Coco: ...@@ -150,10 +150,10 @@ class Coco:
for s in self.sessions: for s in self.sessions:
if not s.stop_evt.is_set(): if not s.stop_evt.is_set():
continue continue
if s.date_finished is None: if s.date_end is None:
self.remove_session(s) self.remove_session(s)
continue continue
delta = datetime.datetime.now() - s.date_finished delta = datetime.datetime.now() - s.date_end
if delta > datetime.timedelta(seconds=interval*5): if delta > datetime.timedelta(seconds=interval*5):
self.remove_session(s) self.remove_session(s)
time.sleep(interval) time.sleep(interval)
......
...@@ -26,7 +26,6 @@ class BaseWebSocketHandler: ...@@ -26,7 +26,6 @@ class BaseWebSocketHandler:
def prepare(self, request): def prepare(self, request):
# self.app = self.settings["app"] # self.app = self.settings["app"]
child, parent = socket.socketpair()
if request.headers.getlist("X-Forwarded-For"): if request.headers.getlist("X-Forwarded-For"):
remote_ip = request.headers.getlist("X-Forwarded-For")[0] remote_ip = request.headers.getlist("X-Forwarded-For")[0]
else: else:
...@@ -36,10 +35,6 @@ class BaseWebSocketHandler: ...@@ -36,10 +35,6 @@ class BaseWebSocketHandler:
self.clients[request.sid]["request"].meta = {"width": self.clients[request.sid]["cols"], self.clients[request.sid]["request"].meta = {"width": self.clients[request.sid]["cols"],
"height": self.clients[request.sid]["rows"]} "height": self.clients[request.sid]["rows"]}
# self.request.__dict__.update(request.__dict__) # self.request.__dict__.update(request.__dict__)
self.clients[request.sid]["client"] = Client(parent, self.clients[request.sid]["request"])
self.clients[request.sid]["proxy"] = WSProxy(self, child, self.clients[request.sid]["room"])
self.app.clients.append(self.clients[request.sid]["client"])
self.clients[request.sid]["forwarder"] = ProxyServer(self.app, self.clients[request.sid]["client"])
def check_origin(self, origin): def check_origin(self, origin):
return True return True
...@@ -64,9 +59,10 @@ class SSHws(Namespace, BaseWebSocketHandler): ...@@ -64,9 +59,10 @@ class SSHws(Namespace, BaseWebSocketHandler):
"cols": int(request.cookies.get('cols', 80)), "cols": int(request.cookies.get('cols', 80)),
"rows": int(request.cookies.get('rows', 24)), "rows": int(request.cookies.get('rows', 24)),
"room": room, "room": room,
"chan": None, # "chan": dict(),
"proxy": None, "proxy": dict(),
"client": None, "client": dict(),
"forwarder": dict(),
"request": None, "request": None,
} }
self.rooms[room] = { self.rooms[room] = {
...@@ -80,18 +76,31 @@ class SSHws(Namespace, BaseWebSocketHandler): ...@@ -80,18 +76,31 @@ class SSHws(Namespace, BaseWebSocketHandler):
self.prepare(request) self.prepare(request)
def on_data(self, message): def on_data(self, message):
if self.clients[request.sid]["proxy"]: if message['room'] and self.clients[request.sid]["proxy"][message['room']]:
self.clients[request.sid]["proxy"].send({"data": message}) self.clients[request.sid]["proxy"][message['room']].send({"data": message['data']})
def on_host(self, message): def on_host(self, message):
# 此处获取主机的信息 # 此处获取主机的信息
uuid = message.get('uuid', None) connection = str(uuid.uuid4())
assetID = message.get('uuid', None)
userid = message.get('userid', None) userid = message.get('userid', None)
if uuid and userid: self.emit('room', {'room': connection, 'secret': message['secret']})
asset = self.app.service.get_asset(uuid)
if assetID and userid:
asset = self.app.service.get_asset(assetID)
system_user = self.app.service.get_system_user(userid) system_user = self.app.service.get_system_user(userid)
if system_user: if system_user:
self.socketio.start_background_task(self.clients[request.sid]["forwarder"].proxy, asset, system_user)
child, parent = socket.socketpair()
self.clients[request.sid]["client"][connection] = Client(parent, self.clients[request.sid]["request"])
self.clients[request.sid]["proxy"][connection] = WSProxy(self, child, self.clients[request.sid]["room"],
connection)
self.app.clients.append(self.clients[request.sid]["client"][connection])
self.clients[request.sid]["forwarder"][connection] = ProxyServer(self.app,
self.clients[request.sid]["client"][connection])
self.socketio.start_background_task(self.clients[request.sid]["forwarder"][connection].proxy, asset,
system_user)
# self.forwarder.proxy(self.asset, system_user) # self.forwarder.proxy(self.asset, system_user)
else: else:
self.on_disconnect() self.on_disconnect()
...@@ -125,13 +134,21 @@ class SSHws(Namespace, BaseWebSocketHandler): ...@@ -125,13 +134,21 @@ class SSHws(Namespace, BaseWebSocketHandler):
def on_disconnect(self): def on_disconnect(self):
self.on_leave(self.clients[request.sid]["room"]) self.on_leave(self.clients[request.sid]["room"])
try: try:
# todo: there maybe have bug del self.clients[request.sid]
self.clients[request.sid]["proxy"].close()
except: except:
pass pass
# self.ssh.close() # self.ssh.close()
pass pass
def on_logout(self, connection):
print("logout", connection)
if connection:
self.clients[request.sid]["proxy"][connection].close()
del self.clients[request.sid]["proxy"][connection]
del self.clients[request.sid]["forwarder"][connection]
self.clients[request.sid]["client"][connection].close()
del self.clients[request.sid]["client"][connection]
class HttpServer: class HttpServer:
# prepare may be rewrite it # prepare may be rewrite it
...@@ -155,7 +172,7 @@ class HttpServer: ...@@ -155,7 +172,7 @@ class HttpServer:
port = self.app.config["HTTPD_PORT"] port = self.app.config["HTTPD_PORT"]
print('Starting websocket server at {}:{}'.format(host, port)) print('Starting websocket server at {}:{}'.format(host, port))
self.socketio.on_namespace(SSHws('/ssh').app(self.app)) self.socketio.on_namespace(SSHws('/ssh').app(self.app))
self.socketio.init_app(self.flask) self.socketio.init_app(self.flask, async_mode="threading")
self.socketio.run(self.flask, port=port, host=host) self.socketio.run(self.flask, port=port, host=host)
def shutdown(self): def shutdown(self):
......
...@@ -28,7 +28,7 @@ class InteractiveServer: ...@@ -28,7 +28,7 @@ class InteractiveServer:
self.client = client self.client = client
self.request = client.request self.request = client.request
self.assets = None self.assets = None
self.search_result = None self._search_result = None
self.asset_groups = None self.asset_groups = None
self.get_user_assets_async() self.get_user_assets_async()
self.get_user_asset_groups_async() self.get_user_asset_groups_async()
...@@ -37,6 +37,18 @@ class InteractiveServer: ...@@ -37,6 +37,18 @@ class InteractiveServer:
def app(self): def app(self):
return self._app() return self._app()
@property
def search_result(self):
if self._search_result:
return self._search_result
else:
return None
@search_result.setter
def search_result(self, value):
value = self.filter_system_users(value)
self._search_result = value
def display_banner(self): def display_banner(self):
self.client.send(char.CLEAR_CHAR) self.client.send(char.CLEAR_CHAR)
logo_path = os.path.join(self.app.root_path, "logo.txt") logo_path = os.path.join(self.app.root_path, "logo.txt")
...@@ -173,12 +185,17 @@ class InteractiveServer: ...@@ -173,12 +185,17 @@ class InteractiveServer:
line = header + '{0.comment:%s}' % (comment_length//2) # comment中可能有中文 line = header + '{0.comment:%s}' % (comment_length//2) # comment中可能有中文
header += "{0.comment:%s}" % comment_length header += "{0.comment:%s}" % comment_length
self.client.send(title(header.format(fake_group, "ID"))) self.client.send(title(header.format(fake_group, "ID")))
for index, group in enumerate(self.asset_groups): for index, group in enumerate(self.asset_groups, 1):
self.client.send(wr(line.format(group, index))) self.client.send(wr(line.format(group, index)))
self.client.send(wr(_("Total: {}").format(len(self.asset_groups)), before=1)) self.client.send(wr(_("Total: {}").format(len(self.asset_groups)), before=1))
def display_group_assets(self, _id): def display_group_assets(self, _id):
self.search_result = self.asset_groups[_id].assets_granted if _id > len(self.asset_groups) or _id <= 0:
self.client.send(wr(warning("Not match group, select again")))
self.display_asset_groups()
return
self.search_result = self.asset_groups[_id-1].assets_granted
self.display_search_result() self.display_search_result()
def display_search_result(self): def display_search_result(self):
...@@ -214,14 +231,13 @@ class InteractiveServer: ...@@ -214,14 +231,13 @@ class InteractiveServer:
def filter_system_users(assets): def filter_system_users(assets):
for asset in assets: for asset in assets:
system_users_granted = asset.system_users_granted system_users_granted = asset.system_users_granted
high_priority = max([s.priority for s in system_users_granted]) high_priority = max([s.priority for s in system_users_granted]) if system_users_granted else 1
system_users_cleaned = [s for s in system_users_granted if s.priority == high_priority] system_users_cleaned = [s for s in system_users_granted if s.priority == high_priority]
asset.system_users_granted = system_users_cleaned asset.system_users_granted = system_users_cleaned
return assets return assets
def get_user_assets(self): def get_user_assets(self):
assets = self.app.service.get_user_assets(self.client.user) self.assets = self.app.service.get_user_assets(self.client.user)
self.assets = self.filter_system_users(assets)
logger.debug("Get user {} assets total: {}".format(self.client.user, len(self.assets))) logger.debug("Get user {} assets total: {}".format(self.client.user, len(self.assets)))
def get_user_assets_async(self): def get_user_assets_async(self):
......
...@@ -186,7 +186,7 @@ class WSProxy: ...@@ -186,7 +186,7 @@ class WSProxy:
``` ```
""" """
def __init__(self, ws, child, room): def __init__(self, ws, child, room, connection):
""" """
:param ws: websocket instance or handler, have write_message method :param ws: websocket instance or handler, have write_message method
:param child: sock child pair :param child: sock child pair
...@@ -196,6 +196,7 @@ class WSProxy: ...@@ -196,6 +196,7 @@ class WSProxy:
self.stop_event = threading.Event() self.stop_event = threading.Event()
self.room = room self.room = room
self.auto_forward() self.auto_forward()
self.connection = connection
def send(self, msg): def send(self, msg):
""" """
...@@ -215,7 +216,7 @@ class WSProxy: ...@@ -215,7 +216,7 @@ class WSProxy:
data = self.child.recv(BUF_SIZE) data = self.child.recv(BUF_SIZE)
if len(data) == 0: if len(data) == 0:
self.close() self.close()
self.ws.emit("data", data.decode("utf-8"), room=self.room) self.ws.emit("data", {'data': data.decode("utf-8"), 'room': self.connection}, room=self.room)
def auto_forward(self): def auto_forward(self):
thread = threading.Thread(target=self.forward, args=()) thread = threading.Thread(target=self.forward, args=())
...@@ -225,4 +226,3 @@ class WSProxy: ...@@ -225,4 +226,3 @@ class WSProxy:
def close(self): def close(self):
self.stop_event.set() self.stop_event.set()
self.child.close() self.child.close()
self.ws.on_disconnect()
...@@ -7,12 +7,13 @@ import threading ...@@ -7,12 +7,13 @@ import threading
import logging import logging
import time import time
import weakref import weakref
import paramiko import paramiko
from paramiko.ssh_exception import SSHException
from .session import Session from .session import Session
from .models import Server from .models import Server
from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \
get_private_key_fingerprint
logger = logging.getLogger(__file__) logger = logging.getLogger(__file__)
...@@ -69,7 +70,6 @@ class ProxyServer: ...@@ -69,7 +70,6 @@ class ProxyServer:
def get_server_conn(self, asset, system_user): def get_server_conn(self, asset, system_user):
logger.info("Connect to {}".format(asset.hostname)) logger.info("Connect to {}".format(asset.hostname))
if not self.validate_permission(asset, system_user): if not self.validate_permission(asset, system_user):
# self.client.send(warning(_('No permission')))
self.client.send(warning('No permission')) self.client.send(warning('No permission'))
return None return None
self.get_system_user_auth(system_user) self.get_system_user_auth(system_user)
...@@ -93,16 +93,22 @@ class ProxyServer: ...@@ -93,16 +93,22 @@ class ProxyServer:
timeout=TIMEOUT, compress=True, auth_timeout=10, timeout=TIMEOUT, compress=True, auth_timeout=10,
look_for_keys=False look_for_keys=False
) )
except paramiko.AuthenticationException: except (paramiko.AuthenticationException, paramiko.BadAuthenticationType):
admins = self.app.config['ADMINS'] or 'administrator' admins = self.app.config['ADMINS'] or 'administrator'
self.client.send(warning(wr( self.client.send(warning(wr(
"Authenticate with server failed, contact {}".format(admins), "Authenticate with server failed, contact {}".format(admins),
before=1, after=0 before=1, after=0
))) )))
key_fingerprint = system_user.private_key.get_hex() if system_user.private_key else None password_short = "None"
key_fingerprint = "None"
if system_user.password:
password_short = system_user.password[:5] + (len(system_user.password)-5) * '*'
if system_user.private_key:
key_fingerprint = get_private_key_fingerprint(system_user.private_key)
logger.error("Connect {}@{}:{} auth failed, password: {}, key: {}".format( logger.error("Connect {}@{}:{} auth failed, password: {}, key: {}".format(
system_user.username, asset.ip, asset.port, system_user.username, asset.ip, asset.port,
system_user.password, key_fingerprint, password_short, key_fingerprint,
)) ))
return None return None
except socket.error as e: except socket.error as e:
...@@ -124,7 +130,10 @@ class ProxyServer: ...@@ -124,7 +130,10 @@ class ProxyServer:
width = self.request.meta.get('width', 80) width = self.request.meta.get('width', 80)
height = self.request.meta.get('height', 24) height = self.request.meta.get('height', 24)
logger.debug("Change win size: %s - %s" % (width, height)) logger.debug("Change win size: %s - %s" % (width, height))
try:
self.server.chan.resize_pty(width=width, height=height) self.server.chan.resize_pty(width=width, height=height)
except SSHException:
break
def watch_win_size_change_async(self): def watch_win_size_change_async(self):
thread = threading.Thread(target=self.watch_win_size_change) thread = threading.Thread(target=self.watch_win_size_change)
......
...@@ -9,6 +9,8 @@ import threading ...@@ -9,6 +9,8 @@ import threading
import paramiko import paramiko
import sys import sys
import time
from .utils import ssh_key_gen from .utils import ssh_key_gen
from .interface import SSHInterface from .interface import SSHInterface
from .interactive import InteractiveServer from .interactive import InteractiveServer
...@@ -48,13 +50,13 @@ class SSHServer: ...@@ -48,13 +50,13 @@ class SSHServer:
try: try:
sock, addr = self.sock.accept() sock, addr = self.sock.accept()
logger.info("Get ssh request from {}: {}".format(addr[0], addr[1])) logger.info("Get ssh request from {}: {}".format(addr[0], addr[1]))
thread = threading.Thread(target=self.handle, args=(sock, addr)) thread = threading.Thread(target=self.handle_connection, args=(sock, addr))
thread.daemon = True thread.daemon = True
thread.start() thread.start()
except Exception as e: except Exception as e:
logger.error("Start SSH server error: {}".format(e)) logger.error("Start SSH server error: {}".format(e))
def handle(self, sock, addr): def handle_connection(self, sock, addr):
transport = paramiko.Transport(sock, gss_kex=False) transport = paramiko.Transport(sock, gss_kex=False)
try: try:
transport.load_server_moduli() transport.load_server_moduli()
...@@ -73,17 +75,23 @@ class SSHServer: ...@@ -73,17 +75,23 @@ class SSHServer:
logger.warning("Handle EOF Error") logger.warning("Handle EOF Error")
return return
chan = transport.accept(10) while True:
chan = transport.accept()
if chan is None: if chan is None:
logger.warning("No ssh channel get") continue
return
server.event.wait(5) server.event.wait(5)
if not server.event.is_set(): if not server.event.is_set():
logger.warning("Client not request a valid request, exiting") logger.warning("Client not request a valid request, exiting")
return return
t = threading.Thread(target=self.handle_chan, args=(chan, request))
t.daemon = True
t.start()
def handle_chan(self, chan, request):
client = Client(chan, request) client = Client(chan, request)
print(chan)
print(request)
self.app.add_client(client) self.app.add_client(client)
self.dispatch(client) self.dispatch(client)
......
...@@ -14,6 +14,7 @@ import time ...@@ -14,6 +14,7 @@ import time
import datetime import datetime
import gettext import gettext
from io import StringIO from io import StringIO
from binascii import hexlify
import paramiko import paramiko
import pyte import pyte
...@@ -26,16 +27,15 @@ from .exception import NoAppException ...@@ -26,16 +27,15 @@ from .exception import NoAppException
BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
def ssh_key_string_to_obj(text): def ssh_key_string_to_obj(text, password=None):
key_f = StringIO(text)
key = None key = None
try: try:
key = paramiko.RSAKey.from_private_key(key_f) key = paramiko.RSAKey.from_private_key(StringIO(text), password=password)
except paramiko.SSHException: except paramiko.SSHException:
pass pass
try: try:
key = paramiko.DSSKey.from_private_key(key_f) key = paramiko.DSSKey.from_private_key(StringIO(text), password=password)
except paramiko.SSHException: except paramiko.SSHException:
pass pass
return key return key
...@@ -357,6 +357,11 @@ def _gettext(): ...@@ -357,6 +357,11 @@ def _gettext():
return gettext.gettext return gettext.gettext
def get_private_key_fingerprint(key):
line = hexlify(key.get_fingerprint())
return b':'.join([line[i:i+2] for i in range(0, len(line), 2)])
def make_message(): def make_message():
os.makedirs(os.path.join(BASE_DIR, "locale", "zh_CN")) os.makedirs(os.path.join(BASE_DIR, "locale", "zh_CN"))
pass pass
......
___
|_ |
| |_ _ _ __ ___ _ __ ___ ___ _ ____ _____ _ __
| | | | | '_ ` _ \| '_ \/ __|/ _ \ '__\ \ / / _ \ '__|
/\__/ / |_| | | | | | | |_) \__ \ __/ | \ V / __/ |
\____/ \__,_|_| |_| |_| .__/|___/\___|_| \_/ \___|_|
| |
|_|
...@@ -28,4 +28,4 @@ tornado==4.5.2 ...@@ -28,4 +28,4 @@ tornado==4.5.2
urllib3==1.22 urllib3==1.22
wcwidth==0.1.7 wcwidth==0.1.7
werkzeug==0.12.2 werkzeug==0.12.2
jumpserver-python-sdk==0.0.22 jumpserver-python-sdk==0.0.23
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment