Unverified Commit b9d9db0e authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #44 from jumpserver/dev

Dev
parents 59ef8545 b9394717
# -*- coding: utf-8 -*-
#
import weakref
import os
import socket
import paramiko
from paramiko.ssh_exception import SSHException
from .utils import get_logger, get_private_key_fingerprint
logger = get_logger(__file__)
TIMEOUT = 10
class SSHConnection:
def __init__(self, app):
self._app = weakref.ref(app)
@property
def app(self):
return self._app()
def get_ssh_client(self, asset, system_user):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
sock = None
self.get_system_user_auth(system_user)
if asset.domain:
sock = self.get_proxy_sock(asset)
try:
ssh.connect(
asset.ip, port=asset.port, username=system_user.username,
password=system_user.password, pkey=system_user.private_key,
timeout=TIMEOUT, compress=True, auth_timeout=TIMEOUT,
look_for_keys=False, sock=sock
)
except (paramiko.AuthenticationException,
paramiko.BadAuthenticationType,
SSHException) as e:
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(
system_user.username, asset.ip, asset.port,
password_short, key_fingerprint,
))
return None, str(e)
except (socket.error, TimeoutError) as e:
return None, str(e)
return ssh, None
def get_transport(self, asset, system_user):
ssh, msg = self.get_ssh_client(asset, system_user)
if ssh:
return ssh.get_transport(), None
else:
return None, msg
def get_channel(self, asset, system_user, term="xterm", width=80, height=24):
ssh, msg = self.get_ssh_client(asset, system_user)
if ssh:
chan = ssh.invoke_shell(term, width=width, height=height)
return chan, None
else:
return None, msg
def get_sftp(self, asset, system_user):
ssh, msg = self.get_ssh_client(asset, system_user)
if ssh:
return ssh.open_sftp(), None
else:
return None, msg
def get_system_user_auth(self, system_user):
"""
获取系统用户的认证信息,密码或秘钥
:return: system user have full info
"""
system_user.password, system_user.private_key = \
self.app.service.get_system_user_auth_info(system_user)
def get_proxy_sock(self, asset):
sock = None
domain = self.app.service.get_domain_detail_with_gateway(
asset.domain
)
if not domain.has_ssh_gateway():
return None
for i in domain.gateways:
gateway = domain.random_ssh_gateway()
proxy_command = [
"ssh", "-o", "StrictHostKeyChecking=no",
"-p", str(gateway.port),
"{}@{}".format(gateway.username, gateway.ip),
"-W", "{}:{}".format(asset.ip, asset.port), "-q",
]
if gateway.password:
proxy_command.insert(0, "sshpass -p {}".format(gateway.password))
if gateway.private_key:
gateway.set_key_dir(os.path.join(self.app.root_path, 'keys'))
proxy_command.append("-i {}".format(gateway.private_key_file))
proxy_command = ' '.join(proxy_command)
try:
sock = paramiko.ProxyCommand(proxy_command)
break
except (paramiko.AuthenticationException,
paramiko.BadAuthenticationType, SSHException,
TimeoutError) as e:
logger.error(e)
continue
return sock
...@@ -159,15 +159,36 @@ class ProxyNamespace(BaseNamespace): ...@@ -159,15 +159,36 @@ class ProxyNamespace(BaseNamespace):
def on_token(self, message): def on_token(self, message):
# 此处获取token含有的主机的信息 # 此处获取token含有的主机的信息
logger.debug("On token trigger") logger.debug("On token trigger")
logger.debug(message)
token = message.get('token', None) token = message.get('token', None)
secret = message.get('secret', None) secret = message.get('secret', None)
host = self.app.service.get_token_asset(token).json() connection = str(uuid.uuid4())
self.emit('room', {'room': connection, 'secret': secret})
if not (token or secret):
logger.debug("token or secret is None")
self.emit('data', {'data': "\nOperation not permitted!", 'room': connection})
self.emit('disconnect')
return None
host = self.app.service.get_token_asset(token)
logger.debug(host) logger.debug(host)
if not host:
logger.debug("host is None")
self.emit('data', {'data': "\nOperation not permitted!", 'room': connection})
self.emit('disconnect')
return None
user_id = host.get('user', None)
logger.debug("self.current_user")
self.current_user = self.app.service.get_user_profile(user_id)
logger.debug(self.current_user)
# { # {
# "user": {UUID}, # "user": {UUID},
# "asset": {UUID}, # "asset": {UUID},
# "system_user": {UUID} # "system_user": {UUID}
# } # }
self.on_host({'secret': secret, 'uuid': host['asset'], 'userid': host['system_user']}) self.on_host({'secret': secret, 'uuid': host['asset'], 'userid': host['system_user']})
def on_resize(self, message): def on_resize(self, message):
......
...@@ -147,7 +147,7 @@ class SSHInterface(paramiko.ServerInterface): ...@@ -147,7 +147,7 @@ class SSHInterface(paramiko.ServerInterface):
self.request.type.append('subsystem') self.request.type.append('subsystem')
self.request.meta.update({'channel': channel.get_id(), 'name': name}) self.request.meta.update({'channel': channel.get_id(), 'name': name})
self.event.set() self.event.set()
return False return super().check_channel_subsystem_request(channel, name)
def check_channel_window_change_request(self, channel, width, height, def check_channel_window_change_request(self, channel, width, height,
pixelwidth, pixelheight): pixelwidth, pixelheight):
......
...@@ -2,19 +2,17 @@ ...@@ -2,19 +2,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import socket
import threading import threading
import time import time
import weakref import weakref
import os
import paramiko
from paramiko.ssh_exception import SSHException from paramiko.ssh_exception import SSHException
from .session import Session from .session import Session
from .models import Server from .models import Server
from .connection import SSHConnection
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, get_logger get_logger
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -62,20 +60,11 @@ class ProxyServer: ...@@ -62,20 +60,11 @@ class ProxyServer:
self.client.user.id, asset.id, system_user.id self.client.user.id, asset.id, system_user.id
) )
def get_system_user_auth(self, system_user):
"""
获取系统用户的认证信息,密码或秘钥
:return: system user have full info
"""
system_user.password, system_user.private_key = \
self.app.service.get_system_user_auth_info(system_user)
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'))
return None return None
self.get_system_user_auth(system_user)
if True: if True:
server = self.get_ssh_server_conn(asset, system_user) server = self.get_ssh_server_conn(asset, system_user)
else: else:
...@@ -86,84 +75,18 @@ class ProxyServer: ...@@ -86,84 +75,18 @@ class ProxyServer:
def get_telnet_server_conn(self, asset, system_user): def get_telnet_server_conn(self, asset, system_user):
pass pass
def get_proxy_sock(self, asset):
sock = None
domain = self.app.service.get_domain_detail_with_gateway(
asset.domain
)
if not domain.has_ssh_gateway():
return None
for i in domain.gateways:
gateway = domain.random_ssh_gateway()
proxy_command = [
"ssh", "-p", str(gateway.port),
"{}@{}".format(gateway.username, gateway.ip),
"-W", "{}:{}".format(asset.ip, asset.port), "-q",
]
if gateway.password:
proxy_command.insert(0, "sshpass -p {}".format(gateway.password))
if gateway.private_key:
gateway.set_key_dir(os.path.join(self.app.root_path, 'keys'))
proxy_command.append("-i {}".format(gateway.private_key_file))
proxy_command = ' '.join(proxy_command)
try:
sock = paramiko.ProxyCommand(proxy_command)
break
except (paramiko.AuthenticationException,
paramiko.BadAuthenticationType, SSHException,
TimeoutError) as e:
logger.error(e)
continue
return sock
def get_ssh_server_conn(self, asset, system_user): def get_ssh_server_conn(self, asset, system_user):
ssh = paramiko.SSHClient() ssh = SSHConnection(self.app)
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
sock = None
if asset.domain:
sock = self.get_proxy_sock(asset)
try:
ssh.connect(
asset.ip, port=asset.port, username=system_user.username,
password=system_user.password, pkey=system_user.private_key,
timeout=TIMEOUT, compress=True, auth_timeout=TIMEOUT,
look_for_keys=False, sock=sock
)
except (paramiko.AuthenticationException, paramiko.BadAuthenticationType, SSHException):
admins = self.app.config['ADMINS'] or 'administrator'
self.client.send(warning(wr(
"Authenticate with server failed, contact {}".format(admins),
before=1, after=0
)))
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(
system_user.username, asset.ip, asset.port,
password_short, key_fingerprint,
))
return None
except (socket.error, TimeoutError) as e:
self.client.send(wr(" {}".format(e)))
return None
finally:
self.connecting = False
self.client.send(b'\r\n')
request = self.client.request request = self.client.request
term = request.meta.get('term', 'xterm') term = request.meta.get('term', 'xterm')
width = request.meta.get('width', 80) width = request.meta.get('width', 80)
height = request.meta.get('height', 24) height = request.meta.get('height', 24)
chan = ssh.invoke_shell(term, width=width, height=height) chan, msg = ssh.get_channel(asset, system_user, term=term,
width=width, height=height)
if not chan:
self.client.send(warning(wr(msg, before=1, after=0)))
self.connecting = False
self.client.send(b'\r\n')
return Server(chan, asset, system_user) return Server(chan, asset, system_user)
def watch_win_size_change(self): def watch_win_size_change(self):
......
import os
import tempfile
import paramiko
import time
from datetime import datetime
from .connection import SSHConnection
class SFTPServer(paramiko.SFTPServerInterface):
root = '/tmp'
def __init__(self, server, **kwargs):
super().__init__(server, **kwargs)
self.server = server
self._sftp = {}
self.hosts = self.get_perm_hosts()
def get_host_sftp(self, host, su):
asset = self.hosts.get(host)
system_user = None
for s in self.get_asset_system_users(host):
if s.name == su:
system_user = s
break
if not asset or not system_user:
raise OSError("No asset or system user explicit")
if host not in self._sftp:
ssh = SSHConnection(self.server.app)
sftp, msg = ssh.get_sftp(asset, system_user)
if sftp:
self._sftp[host] = sftp
return sftp
else:
raise OSError("Can not connect asset sftp server")
else:
return self._sftp[host]
def get_perm_hosts(self):
assets = self.server.app.service.get_user_assets(
self.server.request.user
)
return {asset.hostname: asset for asset in assets}
def parse_path(self, path):
data = path.lstrip('/').split('/')
su = rpath = ''
if len(data) == 1:
host = data[0]
elif len(data) == 2:
host, su = data
rpath = self.root
else:
host, su, *rpath = data
rpath = os.path.join(self.root, '/'.join(rpath))
return host, su, rpath
def get_sftp_rpath(self, path):
host, su, rpath = self.parse_path(path)
sftp = self.get_host_sftp(host, su) if host and su else None
return sftp, rpath
def get_asset_system_users(self, host):
asset = self.hosts.get(host)
if not asset:
return []
return [su for su in asset.system_users_granted if su.protocol == "ssh"]
def su_in_asset(self, su, host):
system_users = self.get_asset_system_users(host)
if su in [s.name for s in system_users]:
return True
else:
return False
def create_ftp_log(self, path, operate, is_success=True, filename=None):
host, su, rpath = self.parse_path(path)
date_start = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") + " +0000",
data = {
"user": self.server.request.user.username,
"asset": host,
"system_user": su,
"remote_addr": self.server.request.addr[0],
"operate": operate,
"filename": filename or rpath,
"date_start": date_start,
"is_success": is_success,
}
for i in range(1, 4):
ok = self.server.app.service.create_ftp_log(data)
if ok:
break
else:
time.sleep(0.5)
continue
@staticmethod
def stat_host_dir():
tmp = tempfile.TemporaryDirectory()
attr = paramiko.SFTPAttributes.from_stat(os.stat(tmp.name))
tmp.cleanup()
return attr
def list_folder(self, path):
output = []
host, su, rpath = self.parse_path(path)
if not host:
for hostname in self.hosts:
attr = self.stat_host_dir()
attr.filename = hostname
output.append(attr)
elif not su:
for su in self.get_asset_system_users(host):
attr = self.stat_host_dir()
attr.filename = su.name
output.append(attr)
else:
sftp, rpath = self.get_sftp_rpath(path)
file_list = sftp.listdir(rpath)
for filename in file_list:
attr = sftp.stat(os.path.join(rpath, filename))
attr.filename = filename
output.append(attr)
return output
def stat(self, path):
host, su, rpath = self.parse_path(path)
e = OSError("Not that dir")
if host and host not in self.hosts:
return paramiko.SFTPServer.convert_errno(e.errno)
if su and not self.su_in_asset(su, host):
return paramiko.SFTPServer.convert_errno(e.errno)
if not rpath or rpath == "/":
attr = self.stat_host_dir()
attr.filename = su or host
return attr
else:
sftp = self.get_host_sftp(host, su)
return sftp.stat(rpath)
def lstat(self, path):
host, su, rpath = self.parse_path(path)
if not rpath or rpath == "/":
attr = self.stat_host_dir()
attr.filename = su or host
else:
sftp = self.get_host_sftp(host, su)
attr = sftp.stat(rpath)
attr.filename = os.path.basename(path)
return attr
def open(self, path, flags, attr):
binary_flag = getattr(os, 'O_BINARY', 0)
flags |= binary_flag
success = False
if flags & os.O_WRONLY:
if flags & os.O_APPEND:
mode = 'ab'
else:
mode = 'wb'
elif flags & os.O_RDWR:
if flags & os.O_APPEND:
mode = 'a+b'
else:
mode = 'r+b'
else:
mode = 'rb'
sftp, rpath = self.get_sftp_rpath(path)
if 'r' in mode:
operate = "Download"
else:
operate = "Upload"
result = None
if sftp is not None:
try:
f = sftp.open(rpath, mode, bufsize=4096)
obj = paramiko.SFTPHandle(flags)
obj.filename = rpath
obj.readfile = f
obj.writefile = f
result = obj
success = True
except OSError:
pass
self.create_ftp_log(path, operate, success)
return result
def remove(self, path):
sftp, rpath = self.get_sftp_rpath(path)
success = False
if sftp is not None:
try:
sftp.remove(rpath)
except OSError as e:
result = paramiko.SFTPServer.convert_errno(e.errno)
else:
result = paramiko.SFTP_OK
success = True
else:
result = paramiko.SFTP_FAILURE
self.create_ftp_log(path, "Delete", success)
return result
def rename(self, src, dest):
host1, su1, rsrc = self.parse_path(src)
host2, su2, rdest = self.parse_path(dest)
success = False
if host1 == host2 and su1 == su2 and host1:
sftp = self.get_host_sftp(host1, su1)
try:
sftp.rename(rsrc, rdest)
success = True
except OSError as e:
result = paramiko.SFTPServer.convert_errno(e.errno)
else:
result = paramiko.SFTP_OK
else:
result = paramiko.SFTP_FAILURE
filename = "{}=>{}".format(rsrc, rdest)
self.create_ftp_log(rsrc, "Rename", success, filename=filename)
return result
def mkdir(self, path, attr):
sftp, rpath = self.get_sftp_rpath(path)
success = False
if sftp is not None and rpath != '/':
try:
sftp.mkdir(rpath)
success = True
except OSError as e:
result = paramiko.SFTPServer.convert_errno(e.errno)
else:
result = paramiko.SFTP_OK
else:
result = paramiko.SFTP_FAILURE
self.create_ftp_log(path, "Mkdir", success)
return result
def rmdir(self, path):
sftp, rpath = self.get_sftp_rpath(path)
success = False
if sftp is not None:
try:
sftp.rmdir(rpath)
success = True
except OSError as e:
result = paramiko.SFTPServer.convert_errno(e.errno)
else:
result = paramiko.SFTP_OK
else:
result = paramiko.SFTP_FAILURE
self.create_ftp_log(path, "Rmdir", success)
return result
# def chattr(self, path, attr):
# sftp, rpath = self.get_sftp_rpath(path)
# if sftp is not None:
# if attr._flags & attr.FLAG_PERMISSIONS:
# sftp.chmod(rpath, attr.st_mode)
# if attr._flags & attr.FLAG_UIDGID:
# sftp.chown(rpath, attr.st_uid, attr.st_gid)
# if attr._flags & attr.FLAG_AMTIME:
# sftp.utime(rpath, (attr.st_atime, attr.st_mtime))
# if attr._flags & attr.FLAG_SIZE:
# sftp.truncate(rpath, attr.st_size)
# return paramiko.SFTP_OK
...@@ -11,6 +11,7 @@ from .utils import ssh_key_gen, get_logger ...@@ -11,6 +11,7 @@ from .utils import ssh_key_gen, get_logger
from .interface import SSHInterface from .interface import SSHInterface
from .interactive import InteractiveServer from .interactive import InteractiveServer
from .models import Client, Request from .models import Client, Request
from .sftp import SFTPServer
logger = get_logger(__file__) logger = get_logger(__file__)
BACKLOG = 5 BACKLOG = 5
...@@ -60,6 +61,9 @@ class SSHServer: ...@@ -60,6 +61,9 @@ class SSHServer:
logger.warning("Failed load moduli -- gex will be unsupported") logger.warning("Failed load moduli -- gex will be unsupported")
transport.add_server_key(self.host_key) transport.add_server_key(self.host_key)
transport.set_subsystem_handler(
'sftp', paramiko.SFTPServer, SFTPServer
)
request = Request(addr) request = Request(addr)
server = SSHInterface(self.app, request) server = SSHInterface(self.app, request)
try: try:
...@@ -100,7 +104,10 @@ class SSHServer: ...@@ -100,7 +104,10 @@ class SSHServer:
if 'pty' in request_type: if 'pty' in request_type:
logger.info("Request type `pty`, dispatch to interactive mode") logger.info("Request type `pty`, dispatch to interactive mode")
InteractiveServer(self.app, client).interact() InteractiveServer(self.app, client).interact()
elif 'subsystem' in request_type:
pass
else: else:
logger.info("Request type `{}`".format(request_type))
client.send("Not support request type: %s" % request_type) client.send("Not support request type: %s" % request_type)
def shutdown(self): def shutdown(self):
......
...@@ -20,7 +20,7 @@ Jinja2==2.10 ...@@ -20,7 +20,7 @@ Jinja2==2.10
jmespath==0.9.3 jmespath==0.9.3
jms-es-sdk==0.5.2 jms-es-sdk==0.5.2
jms-storage==0.0.12 jms-storage==0.0.12
jumpserver-python-sdk==0.0.35 jumpserver-python-sdk==0.0.39
MarkupSafe==1.0 MarkupSafe==1.0
oss2==2.4.0 oss2==2.4.0
paramiko==2.4.0 paramiko==2.4.0
......
libffi-devel libffi-devel sshpass
\ No newline at end of file \ No newline at end of file
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