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):
def on_token(self, message):
# 此处获取token含有的主机的信息
logger.debug("On token trigger")
logger.debug(message)
token = message.get('token', 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)
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},
# "asset": {UUID},
# "system_user": {UUID}
# }
self.on_host({'secret': secret, 'uuid': host['asset'], 'userid': host['system_user']})
def on_resize(self, message):
......
......@@ -147,7 +147,7 @@ class SSHInterface(paramiko.ServerInterface):
self.request.type.append('subsystem')
self.request.meta.update({'channel': channel.get_id(), 'name': name})
self.event.set()
return False
return super().check_channel_subsystem_request(channel, name)
def check_channel_window_change_request(self, channel, width, height,
pixelwidth, pixelheight):
......
......@@ -2,19 +2,17 @@
# -*- coding: utf-8 -*-
#
import socket
import threading
import time
import weakref
import os
import paramiko
from paramiko.ssh_exception import SSHException
from .session import Session
from .models import Server
from .connection import SSHConnection
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__)
......@@ -62,20 +60,11 @@ class ProxyServer:
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):
logger.info("Connect to {}".format(asset.hostname))
if not self.validate_permission(asset, system_user):
self.client.send(warning('No permission'))
return None
self.get_system_user_auth(system_user)
if True:
server = self.get_ssh_server_conn(asset, system_user)
else:
......@@ -86,84 +75,18 @@ class ProxyServer:
def get_telnet_server_conn(self, asset, system_user):
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):
ssh = paramiko.SSHClient()
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')
ssh = SSHConnection(self.app)
request = self.client.request
term = request.meta.get('term', 'xterm')
width = request.meta.get('width', 80)
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)
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
from .interface import SSHInterface
from .interactive import InteractiveServer
from .models import Client, Request
from .sftp import SFTPServer
logger = get_logger(__file__)
BACKLOG = 5
......@@ -60,6 +61,9 @@ class SSHServer:
logger.warning("Failed load moduli -- gex will be unsupported")
transport.add_server_key(self.host_key)
transport.set_subsystem_handler(
'sftp', paramiko.SFTPServer, SFTPServer
)
request = Request(addr)
server = SSHInterface(self.app, request)
try:
......@@ -100,7 +104,10 @@ class SSHServer:
if 'pty' in request_type:
logger.info("Request type `pty`, dispatch to interactive mode")
InteractiveServer(self.app, client).interact()
elif 'subsystem' in request_type:
pass
else:
logger.info("Request type `{}`".format(request_type))
client.send("Not support request type: %s" % request_type)
def shutdown(self):
......
......@@ -20,7 +20,7 @@ Jinja2==2.10
jmespath==0.9.3
jms-es-sdk==0.5.2
jms-storage==0.0.12
jumpserver-python-sdk==0.0.35
jumpserver-python-sdk==0.0.39
MarkupSafe==1.0
oss2==2.4.0
paramiko==2.4.0
......
libffi-devel
\ No newline at end of file
libffi-devel sshpass
\ 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