Commit 73a012f8 authored by ibuler's avatar ibuler

[Fixture] 添加app service, user service

parent c5e0e537
44d8134f-3153-4f9e-b601-50c0fc73eb3a:75cc67b1-4483-413f-babb-3b522ffe951b
\ No newline at end of file
......@@ -5,9 +5,8 @@ import time
from flask import Flask
import socketio
from jms import AppService, UserService
from jms.mixin import AppMixin
from .conf import config
from .service import service
logger = logging.getLogger(__file__)
......@@ -16,22 +15,10 @@ logger = logging.getLogger(__file__)
__version__ = '0.4.0'
class Luna(Flask, AppMixin):
app_service = None
class Luna(Flask):
app_service = service
clients = {}
def bootstrap(self):
self.app_service = AppService(
app_name=self.config['NAME'],
endpoint=self.config['JUMPSERVER_ENDPOINT'])
self.app_auth()
while True:
if self.check_auth():
logging.info('App auth passed')
break
else:
logging.warn('App auth failed, Access key error or need admin active it')
time.sleep(5)
active = True
def run(self, host=None, port=None, debug=None, **options):
print(time.ctime())
......@@ -43,14 +30,14 @@ class Luna(Flask, AppMixin):
return Flask.run(self, host=host, port=port, debug=debug, **options)
@classmethod
def stop(cls):
for i in cls.clients:
def stop(self):
self.active = False
for i in Luna.clients:
i.disconnect()
socket_io.stop()
async_mode = 'threading'
app = Luna(__name__, template_folder='dist')
app.config.update(**config)
socket_io = socketio.Server(logger=True, async_mode=async_mode)
socket_io = socketio.Server(logger=False, async_mode=async_mode)
app.wsgi_app = socketio.Middleware(socket_io, app.wsgi_app)
......@@ -9,31 +9,33 @@ from jms import UserService
from . import app
def is_authenticate():
def is_authenticate(user_service):
pass
def login_required(login_url=None):
if login_url is None:
endpoint = app.config['JUMPSERVER_ENDPOINT']
login_url = endpoint.rstrip('/') + '/users/login?next=' + request.url
def login_required(func=None, login_url=None):
if func is None:
return partial(login_required, login_url=login_url)
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
session_id = request.cookies.get('sessionid', '')
csrf_token = request.cookies.get('csrf_token', '')
if '' in [session_id, csrf_token]:
return redirect(login_url)
g.user_service = UserService.auth_from_session(session_id, csrf_token)
if g.user_service.is_authenticate():
return func(*args, **kwargs)
else:
return redirect(login_url)
return wrapper
return decorate
@wraps(func)
def wrapper(*args, **kwargs):
url = login_url
if url is None:
endpoint = app.config['JUMPSERVER_ENDPOINT']
url = endpoint.rstrip('/') + '/users/login?next=' + request.url
session_id = request.cookies.get('sessionid', '')
csrf_token = request.cookies.get('csrftoken', '')
if '' in [session_id, csrf_token]:
return redirect(url)
g.user_service = UserService(endpoint=app.config['JUMPSERVER_ENDPOINT'])
g.user_service.auth_from_session(session_id, csrf_token)
if g.user_service.is_authenticated():
return func(*args, **kwargs)
else:
return redirect(login_url)
return wrapper
......
# ~*~ coding: utf-8 ~*~
#
import os
import logging
from logging import StreamHandler
from logging.handlers import TimedRotatingFileHandler
from .conf import config
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
LOG_LEVELS = {
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'WARN': logging.WARNING,
'WARNING': logging.WARNING,
'ERROR': logging.ERROR,
'FATAL': logging.FATAL,
'CRITICAL': logging.CRITICAL,
}
def create_logger():
level = config.get('LOG_LEVEL', None)
level = LOG_LEVELS.get(level, logging.INFO)
log_dir = config.get('LOG_DIR', os.path.join(PROJECT_DIR, 'logs'))
log_path = os.path.join(log_dir, 'luna.log')
logger_root = logging.getLogger()
logger = logging.getLogger(config.get('NAME', 'luna'))
main_formatter = logging.Formatter(
fmt='%(asctime)s [%(module)s %(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
console_handler = StreamHandler()
file_handler = TimedRotatingFileHandler(
filename=log_path, when='D', backupCount=10)
for handler in [console_handler, file_handler]:
handler.setFormatter(main_formatter)
logger.addHandler(handler)
logger_root.addHandler(console_handler)
logger_root.setLevel(logging.WARNING)
logger.setLevel(level)
def get_logger(name):
return logging.getLogger('luna.%s' % name)
create_logger()
# # ~*~ coding: utf-8 ~*~
# #
#
# import os
# import logging
# from logging import StreamHandler
# from logging.handlers import TimedRotatingFileHandler
#
# from .conf import config
# PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
#
#
# LOG_LEVELS = {
# 'DEBUG': logging.DEBUG,
# 'INFO': logging.INFO,
# 'WARN': logging.WARNING,
# 'WARNING': logging.WARNING,
# 'ERROR': logging.ERROR,
# 'FATAL': logging.FATAL,
# 'CRITICAL': logging.CRITICAL,
# }
#
#
# def create_logger():
# level = config.get('LOG_LEVEL', None)
# level = LOG_LEVELS.get(level, logging.INFO)
# log_dir = config.get('LOG_DIR', os.path.join(PROJECT_DIR, 'logs'))
# log_path = os.path.join(log_dir, 'luna.log')
# logger_root = logging.getLogger()
# logger = logging.getLogger(config.get('NAME', 'luna'))
#
# main_formatter = logging.Formatter(
# fmt='%(asctime)s [%(module)s %(levelname)s] %(message)s',
# datefmt='%Y-%m-%d %H:%M:%S')
# console_handler = StreamHandler()
# file_handler = TimedRotatingFileHandler(
# filename=log_path, when='D', backupCount=10)
#
# for handler in [console_handler, file_handler]:
# handler.setFormatter(main_formatter)
# logger.addHandler(handler)
# logger_root.addHandler(console_handler)
# logger_root.setLevel(logging.WARNING)
# logger.setLevel(level)
#
#
# def get_logger(name):
# return logging.getLogger('luna.%s' % name)
#
# # create_logger()
# ~*~ coding: utf-8 ~*~
import socket
import threading
import paramiko
import re
import time
import logging
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
import select
from flask import request, g
from jms.utils import TtyIOParser
from .tasks import command_queue, record_queue
logger = logging.getLogger(__file__)
class ProxyChannel(object):
serial = 1
def __init__(self):
object.__setattr__(self, 'f', StringIO())
lock = threading.Lock()
lock.acquire()
try:
ProxyChannel.serial += 1
object.__setattr__(self, 'serial', ProxyChannel.serial)
finally:
lock.release()
def send(self, s):
return self.f.write(s)
def recv(self, size):
return self.f.read(size)
def fileno(self):
return self.serial
def __getattr__(self, item):
return getattr(self.f, item)
def __setattr__(self, key, value):
return setattr(self.f, key, value)
class ProxyServer(object):
"""
We are using this class proxy client channel (user) with backend channel
When receive client input command, send to backend ssh channel
and when receive output of command from backend, send to client
We also record the command and result to database for audit
"""
ENTER_CHAR = ['\r', '\n', '\r\n']
OUTPUT_END_PATTERN = re.compile(r'\x1b]0;.+@.+:.+\x07.*')
VIM_PATTERN = re.compile(r'\x1b\[\?1049', re.X)
IGNORE_OUTPUT_COMMAND = [re.compile(r'^cat\s+'),
re.compile(r'^tailf?\s+')]
def __init__(self, app, asset, system_user):
self.app = app
self.asset = asset
self.system_user = system_user
self.service = app.service
self.backend_channel = None
self.ssh = None
# If is first input, will clear the output data: ssh banner and PS1
self.is_first_input = True
self.in_input_state = False
# This ssh session command serial no
self.in_vim_state = False
self.command_no = 1
self.input = ''
self.output = ''
self.output_data = []
self.input_data = []
self.history = {}
def is_finish_input(self, s):
for char in s:
if char in self.ENTER_CHAR:
return True
return False
def get_output(self):
parser = TtyIOParser(width=request.win_width,
height=request.win_height)
self.output = parser.parse_output(b''.join(self.output_data))
if self.input:
data = {
'proxy_log_id': g.proxy_log_id,
'user': request.user.username,
'asset': request.asset.ip,
'system_user': request.system_user.username,
'command_no': self.command_no,
'command': self.input,
'output': self.output[:100],
'timestamp': time.time(),
}
command_queue.put(data)
self.command_no += 1
def get_input(self):
parser = TtyIOParser(width=request.win_width,
height=request.win_height)
self.input = parser.parse_input(b''.join(self.input_data))
# Todo: App check user permission
def validate_user_asset_permission(self, user_id, asset_id, system_user_id):
return self.service.validate_user_asset_permission(
user_id, asset_id, system_user_id)
def get_asset_auth(self, system_user):
return self.service.get_system_user_auth_info(system_user)
def connect(self, term=b'xterm', width=80, height=24, timeout=10):
asset = self.asset
system_user = self.system_user
if not self.validate_user_asset_permission(
request.user.id, asset.id, system_user.id):
logger.warning('User %s have no permission connect %s with %s' %
(request.user.username,
asset.ip, system_user.username))
return None
self.ssh = ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
password, private_key = self.get_asset_auth(self.system_user)
data = {"user": request.user.username, "asset": self.asset.ip,
"system_user": self.system_user.username, "login_type": "ST",
"date_start": time.time(), "is_failed": 0}
g.proxy_log_id = proxy_log_id = self.service.send_proxy_log(data)
try:
g.client_channel.send(
wr('Connecting %s@%s:%s ... ' %
(system_user.username, asset.ip, asset.port)))
ssh.connect(hostname=asset.ip, port=asset.port,
username=system_user.username,
password=password, pkey=private_key,
look_for_keys=False, allow_agent=True,
compress=True, timeout=timeout)
except (paramiko.AuthenticationException,
paramiko.ssh_exception.SSHException):
msg = 'Connect backend server %s failed: %s' \
% (asset.ip, 'Auth failed')
logger.warning(msg)
failed = True
except socket.error:
msg = 'Connect asset %s failed: %s' % (asset.ip, 'Timeout')
logger.warning(msg)
failed = True
else:
msg = 'Connect asset %(username)s@%(host)s:%(port)s successfully' % {
'username': system_user.username,
'host': asset.ip,
'port': asset.port}
failed = False
logger.info(msg)
if failed:
g.client_channel.send(wr(warning(msg+'\r\n')))
data = {
"proxy_log_id": proxy_log_id,
"date_finished": time.time(),
"was_failed": 1
}
self.service.finish_proxy_log(data)
return None
self.backend_channel = channel = ssh.invoke_shell(
term=term, width=width, height=height)
channel.settimeout(100)
return channel
def is_match_ignore_command(self, data):
for pattern in self.IGNORE_OUTPUT_COMMAND:
if pattern.match(data):
return True
return False
def proxy(self):
self.backend_channel = backend_channel = self.connect()
self.app.proxy_list[g.proxy_log_id] = [g.client_channel, backend_channel]
if backend_channel is None:
return
while True:
try:
r, w, x = select.select([g.client_channel, backend_channel], [], [])
except:
pass
if request.change_win_size_event.is_set():
request.change_win_size_event.clear()
backend_channel.resize_pty(width=request.win_width,
height=request.win_height)
if g.client_channel in r:
# Get output of the command
self.is_first_input = False
if self.in_input_state is False:
self.get_output()
del self.output_data[:]
self.in_input_state = True
client_data = g.client_channel.recv(1024)
if self.is_finish_input(client_data):
self.in_input_state = False
self.get_input()
del self.input_data[:]
if len(client_data) == 0:
logger.info('Logout from ssh server %(host)s: %(username)s' % {
'host': request.environ['REMOTE_ADDR'],
'username': request.user.username,
})
break
backend_channel.send(client_data)
if backend_channel in r:
backend_data = backend_channel.recv(1024)
if self.in_input_state:
self.input_data.append(backend_data)
else:
self.output_data.append(backend_data)
if len(backend_data) == 0:
g.client_channel.send(
wr('Disconnect from %s' % request.asset.ip))
logger.info('Logout from asset %(host)s: %(username)s' % {
'host': request.asset.ip,
'username': request.user.username,
})
break
g.client_channel.send(backend_data)
if self.is_match_ignore_command(self.input):
output = 'ignore output ...'
else:
output = backend_data
record_data = {
'proxy_log_id': g.proxy_log_id,
'output': output,
'timestamp': time.time(),
}
record_queue.put(record_data)
data = {
"proxy_log_id": g.proxy_log_id,
"date_finished": time.time(),
}
self.service.finish_proxy_log(data)
# ~*~ coding: utf-8 ~*~
# ~*~ coding: utf-8 ~*~
import time
import logging
from jms import AppService
from jms.exceptions import LoadAccessKeyError
from .conf import config
logger = logging.getLogger(__name__)
service = AppService(
app_name=config.get('NAME'),
endpoint=config.get('JUMPSERVER_ENDPOINT'),
config=config)
def auth_it():
try:
service.auth_magic()
except LoadAccessKeyError:
is_success, content = service.register_terminal()
if is_success:
service.access_key.id = content.access_key_id
service.access_key.secret = content.access_key_secret
service.access_key.save_to_key_store()
service.auth()
else:
raise SystemExit('Register terminal failed, may be '
'have been exist, you should look for '
'the terminal access key, set in config, '
'or put it in access key store'
)
print('Using access key %s:***' % service.access_key.id)
while True:
if service.is_authenticated():
logger.info('App auth passed')
break
else:
logger.warn('App auth failed, Access key error '
'or need admin active it')
time.sleep(5)
auth_it()
# ~*~ coding: utf-8 ~*~
from __future__ import absolute_import
from jms.tasks import MemoryQueue, Task
from .service import service
command_queue = MemoryQueue()
record_queue = MemoryQueue()
command_task = Task(command_queue, service.send_command_log,
threads_num=4, batch_count=10)
record_task = Task(record_queue, service.send_record_log,
threads_num=4, batch_count=10)
......@@ -3,23 +3,33 @@
from .. import app
from ..authentication import login_required
from flask import render_template, send_from_directory
from flask import render_template, send_from_directory, make_response
# __all__ = ['index', 'luna', 'send_dist']
__all__ = ['index', 'luna', 'send_dist']
@app.route('/test/')
@login_required
def test():
return make_response('Hello')
@app.route('/')
@login_required
def index():
return render_template('index.html')
@login_required
@app.route('/luna/')
@login_required
def luna():
return render_template('index.html')
@app.route('/luna/<path:path>')
@login_required
def send_dist(path):
return send_from_directory('dist', path)
# ~*~ coding: utf-8 ~*~
#!/usr/bin/env python
# coding: utf-8
import re
import select
import threading
import socket
import collections
import json
import logging
import paramiko
import time
from flask import request, g
from jms.utils import TtyIOParser
from .. import app, socket_io
from ..nav import nav
from ..tasks import command_queue, record_queue
clients = app.clients
logger = logging.getLogger(__file__)
......@@ -39,7 +41,7 @@ def handle_machine(sid, message):
clients[sid]['host'] = host = '120.25.240.109'
clients[sid]['port'] = port = 8022
t = threading.Thread(target=forward, args=(sid,))
t.setDaemon(True)
t.daemon = True
t.start()
socket_io.emit('data', 'Connect to %s:%s \r\n' % (host, port), room=sid)
......@@ -62,6 +64,8 @@ def handle_term_resize(sid, json):
logger.debug('Resize term: %s' % json)
def forward(sid):
try:
host = clients[sid]['host']
......@@ -74,10 +78,11 @@ def forward(sid):
ssh.connect(host, port=port, username='jms', password='redhat')
clients[sid]['ssh'] = ssh
clients[sid]['chan'] = chan = ssh.invoke_shell()
while True:
while app.active:
r, w, x = select.select([chan], [], [])
if chan in r:
data = chan.recv(1024)
if not len(data):
break
socket_io.emit('data', data, room=sid)
del clients[sid]
......@@ -2,6 +2,7 @@
# ~*~ coding: utf-8 ~*~
from luna import app
import os
host = app.config['BIND_HOST']
......@@ -9,6 +10,12 @@ port = app.config['LISTEN_PORT']
if __name__ == '__main__':
try:
os.mkdir('logs')
os.mkdir('keys')
except Exception:
pass
try:
app.run(threaded=True, host=host, port=port)
except KeyboardInterrupt:
......
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