Commit 92a50b56 authored by ibuler's avatar ibuler

[Feature] 完成app注册sdk对接

parent e9f6547d
...@@ -5,10 +5,9 @@ import logging ...@@ -5,10 +5,9 @@ import logging
from .config import Config from .config import Config
from .sshd import SSHServer from .sshd import SSHServer
from .ws import WSServer from .httpd import HttpServer
from .logging import create_logger from .logging import create_logger
from .sdk import AppService from .sdk import AppService
from .auth import AppAccessKey
__version__ = '0.4.0' __version__ = '0.4.0'
...@@ -27,6 +26,7 @@ class Coco: ...@@ -27,6 +26,7 @@ class Coco:
'SSHD_PORT': 2222, 'SSHD_PORT': 2222,
'WS_PORT': 5000, 'WS_PORT': 5000,
'ACCESS_KEY': '', 'ACCESS_KEY': '',
'ACCESS_KEY_ENV': 'COCO_ACCESS_KEY',
'ACCESS_KEY_FILE': os.path.join(BASE_DIR, 'keys', '.access_key'), 'ACCESS_KEY_FILE': os.path.join(BASE_DIR, 'keys', '.access_key'),
'SECRET_KEY': None, 'SECRET_KEY': None,
'LOG_LEVEL': 'INFO', 'LOG_LEVEL': 'INFO',
...@@ -43,7 +43,6 @@ class Coco: ...@@ -43,7 +43,6 @@ class Coco:
self.config = self.config_class(BASE_DIR, defaults=self.default_config) self.config = self.config_class(BASE_DIR, defaults=self.default_config)
self.sessions = [] self.sessions = []
self.clients = [] self.clients = []
self.ws = None
self.root_path = root_path self.root_path = root_path
self.name = name self.name = name
self.lock = threading.Lock() self.lock = threading.Lock()
...@@ -55,7 +54,7 @@ class Coco: ...@@ -55,7 +54,7 @@ class Coco:
if root_path is None: if root_path is None:
self.root_path = BASE_DIR self.root_path = BASE_DIR
self.make_logger() self.httpd = None
self.sshd = None self.sshd = None
self.running = True self.running = True
...@@ -63,8 +62,9 @@ class Coco: ...@@ -63,8 +62,9 @@ class Coco:
create_logger(self) create_logger(self)
def prepare(self): def prepare(self):
self.make_logger()
self.sshd = SSHServer(self) self.sshd = SSHServer(self)
self.ws = WSServer(self) self.httpd = HttpServer(self)
self.initial_service() self.initial_service()
def heartbeat(self): def heartbeat(self):
...@@ -94,7 +94,7 @@ class Coco: ...@@ -94,7 +94,7 @@ class Coco:
thread.start() thread.start()
def run_ws(self): def run_ws(self):
thread = threading.Thread(target=self.ws.run, args=()) thread = threading.Thread(target=self.httpd.run, args=())
thread.daemon = True thread.daemon = True
thread.start() thread.start()
...@@ -123,6 +123,7 @@ class Coco: ...@@ -123,6 +123,7 @@ class Coco:
def initial_service(self): def initial_service(self):
self.service = AppService(self) self.service = AppService(self)
self.service.initial()
def monitor_session(self): def monitor_session(self):
pass pass
......
...@@ -10,19 +10,6 @@ from . import utils ...@@ -10,19 +10,6 @@ from . import utils
from .exception import LoadAccessKeyError from .exception import LoadAccessKeyError
def make_signature(access_key_secret, date=None):
if isinstance(date, bytes):
date = date.decode("utf-8")
if isinstance(date, int):
date_gmt = utils.http_date(date)
elif date is None:
date_gmt = utils.http_date(int(time.time()))
else:
date_gmt = date
data = str(access_key_secret) + "\n" + date_gmt
return utils.content_md5(data)
class AccessKeyAuth(object): class AccessKeyAuth(object):
def __init__(self, access_key): def __init__(self, access_key):
self.id = access_key.id self.id = access_key.id
...@@ -34,49 +21,6 @@ class AccessKeyAuth(object): ...@@ -34,49 +21,6 @@ class AccessKeyAuth(object):
req.headers['Authorization'] = "Sign {0}:{1}".format(self.id, signature) req.headers['Authorization'] = "Sign {0}:{1}".format(self.id, signature)
return req return req
#
# class AccessTokenAuth(object):
# def __init__(self, token):
# self.token = token
#
# def sign_request(self, req):
# req.headers['Authorization'] = 'Bearer {0}'.format(self.token)
# return req
#
#
# class SessionAuth(object):
# def __init__(self, session_id, csrf_token):
# self.session_id = session_id
# self.csrf_token = csrf_token
#
# def sign_request(self, req):
# cookie = [v for v in req.headers.get('Cookie', '').split(';')
# if v.strip()]
# cookie.extend(['sessionid='+self.session_id,
# 'csrftoken='+self.csrf_token])
# req.headers['Cookie'] = ';'.join(cookie)
# req.headers['X-CSRFTOKEN'] = self.csrf_token
# return req
# class Auth(object):
# def __init__(self, token=None, access_key_id=None,
# access_key_secret=None,
# session_id=None, csrf_token=None):
#
# if token is not None:
# self.instance = AccessTokenAuth(token)
# elif access_key_id and access_key_secret:
# self.instance = AccessKeyAuth(access_key_id, access_key_secret)
# elif session_id and csrf_token:
# self.instance = SessionAuth(session_id, csrf_token)
# else:
# raise SyntaxError('Need token or access_key_id, access_key_secret '
# 'or session_id, csrf_token')
#
# def sign_request(self, req):
# return self.instance.sign_request(req)
class AccessKey(object): class AccessKey(object):
def __init__(self, id=None, secret=None): def __init__(self, id=None, secret=None):
...@@ -97,13 +41,13 @@ class AccessKey(object): ...@@ -97,13 +41,13 @@ class AccessKey(object):
@classmethod @classmethod
def load_from_val(cls, val, **kwargs): def load_from_val(cls, val, **kwargs):
id, secret = cls.clean(val, **kwargs) id, secret = cls.clean(val, **kwargs)
return cls(id, secret) return cls(id=id, secret=secret)
@classmethod @classmethod
def load_from_env(cls, env, **kwargs): def load_from_env(cls, env, **kwargs):
value = os.environ.get(env) value = os.environ.get(env)
id, secret = cls.clean(value, **kwargs) id, secret = cls.clean(value, **kwargs)
return cls(id, secret) return cls(id=id, secret=secret)
@classmethod @classmethod
def load_from_f(cls, f, **kwargs): def load_from_f(cls, f, **kwargs):
...@@ -117,7 +61,7 @@ class AccessKey(object): ...@@ -117,7 +61,7 @@ class AccessKey(object):
break break
f.close() f.close()
id, secret = cls.clean(value, **kwargs) id, secret = cls.clean(value, **kwargs)
return cls(id, secret) return cls(id=id, secret=secret)
def save_to_f(self, f, silent=False): def save_to_f(self, f, silent=False):
if isinstance(f, str): if isinstance(f, str):
...@@ -131,44 +75,57 @@ class AccessKey(object): ...@@ -131,44 +75,57 @@ class AccessKey(object):
finally: finally:
f.close() f.close()
def __nonzero__(self): def __bool__(self):
return bool(self.id and self.secret) return bool(self.id and self.secret)
__bool__ = __nonzero__
def __str__(self): def __str__(self):
return '{0}:{1}'.format(self.id, self.secret) return '{0}:{1}'.format(self.id, self.secret)
__repr__ = __str__
def __repr__(self):
return '{0}:{1}'.format(self.id, self.secret)
class AppAccessKey(AccessKey): class AppAccessKey(AccessKey):
"""使用Access key来认证""" """使用Access key来认证"""
def __init__(self, app, id=None, secret=None): def __init__(self, id=None, secret=None):
super().__init__(id=id, secret=secret) super().__init__(id=id, secret=secret)
self.app = None
def set_app(self, app):
self.app = app self.app = app
self._key_store = app.config['ACCESS_KEY_STORE']
self._key_env = app.config['ACCESS_KEY_ENV'] @property
self._key_val = app.config['ACCESS_KEY'] def _key_env(self):
return self.app.config['ACCESS_KEY_ENV']
@property
def _key_val(self):
return self.app.config['ACCESS_KEY']
@property
def _key_file(self):
return self.app.config['ACCESS_KEY_FILE']
def load_from_conf_env(self, sep=':', silent=False): def load_from_conf_env(self, sep=':', silent=False):
return super().load_from_env(self._key_env, sep=sep, silent=silent) return super().load_from_env(self._key_env, sep=sep, silent=silent)
def load_from_conf(self, sep=':', silent=False): def load_from_conf_val(self, sep=':', silent=False):
return super().load_from_val(self._key_val, sep=sep, silent=silent) return super().load_from_val(self._key_val, sep=sep, silent=silent)
def load_from_key_store(self, sep=':', silent=False): def load_from_conf_file(self, sep=':', silent=False):
return super().load_from_f(self._key_store, sep=sep, silent=silent) return super().load_from_f(self._key_file, sep=sep, silent=silent)
def load(self, **kwargs): def load(self, **kwargs):
"""Should return access_key_id, access_key_secret""" """Should return access_key_id, access_key_secret"""
for method in [self.load_from_env, for method in [self.load_from_conf_env,
self.load_from_conf, self.load_from_conf_val,
self.load_from_key_store]: self.load_from_conf_file]:
try: try:
return method(**kwargs) return method(**kwargs)
except LoadAccessKeyError: except LoadAccessKeyError:
continue continue
return None return None
def save_to_key_store(self): def save_to_file(self):
return super().save_to_f(self._key_store) return super().save_to_f(self._key_file)
\ No newline at end of file \ No newline at end of file
...@@ -72,7 +72,7 @@ class MonitorWehSocketHandler(BaseWehSocketHandler): ...@@ -72,7 +72,7 @@ class MonitorWehSocketHandler(BaseWehSocketHandler):
pass pass
class WSServer: class HttpServer:
routers = [ routers = [
(r'/ws/interactive/', InteractiveWehSocketHandler), (r'/ws/interactive/', InteractiveWehSocketHandler),
(r'/ws/proxy/(?P<asset_id>[0-9]+)/(?P<system_user_id>[0-9]+)/', ProxyWehSocketHandler), (r'/ws/proxy/(?P<asset_id>[0-9]+)/(?P<system_user_id>[0-9]+)/', ProxyWehSocketHandler),
......
...@@ -80,7 +80,7 @@ class AppRequests(object): ...@@ -80,7 +80,7 @@ class AppRequests(object):
@staticmethod @staticmethod
def clean_result(resp): def clean_result(resp):
if resp.status_code >= 500: if resp.status_code >= 500:
raise ResponseError("Response code is {0.code}: {0.text}".format(resp)) raise ResponseError("Response code is {0.status_code}: {0.text}".format(resp))
try: try:
_ = resp.json() _ = resp.json()
...@@ -138,82 +138,85 @@ class AppService: ...@@ -138,82 +138,85 @@ class AppService:
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
self.access_key = None self.access_key = None
self.requests = AppRequests(app.config['JUMPSERVER_ENDPOINT']) self.requests = AppRequests(app.config['CORE_HOST'])
self.prepare()
def prepare(self): def initial(self):
self.load_access_key() self.load_access_key()
self.set_auth() self.set_auth()
self.valid_auth() self.valid_auth()
def load_access_key(self): def load_access_key(self):
# Must be get access key if not register it # Must be get access key if not register it
self.access_key = self.access_key_class(self.app).load() self.access_key = self.access_key_class()
self.access_key.set_app(self.app)
self.access_key = self.access_key.load()
if self.access_key is None: if self.access_key is None:
logger.info("No access key found, register it")
self.register_and_save() self.register_and_save()
def set_auth(self): def set_auth(self):
self.requests.auth = AccessKeyAuth(self.access_key) self.requests.auth = AccessKeyAuth(self.access_key)
def valid_auth(self): def valid_auth(self):
while True:
delay = 1 delay = 1
while delay < 300:
if self.heatbeat() is None: if self.heatbeat() is None:
msg = "Access key is not valid or need admin " \ msg = "Access key is not valid or need admin " \
"accepted, waiting %d s" % delay "accepted, waiting %d s" % delay
logger.info(msg) logger.info(msg)
delay += 1 delay += 3
time.sleep(1) time.sleep(3)
else: else:
break break
if delay >= 300:
logger.info("Start timeout")
sys.exit()
def register_and_save(self): def register_and_save(self):
self.register() self.register()
self.save_access_key() self.save_access_key()
def save_access_key(self): def save_access_key(self):
self.access_key.save_to_key_store() self.access_key.save_to_file()
def register(self): def register(self):
try: try:
resp = self.requests.post('terminal-register', resp = self.requests.post(
data={'name': self.app.name}, 'terminal-register', data={'name': self.app.name}, use_auth=False
use_auth=False) )
except (RequestError, ResponseError) as e: except (RequestError, ResponseError) as e:
logger.error(e) logger.error(e)
sys.exit() return
if resp.status_code == 201: if resp.status_code == 201:
access_key = resp.json()['access_key'] access_key = resp.json()['access_key']
access_key_id = access_key['id'] access_key_id = access_key['id']
access_key_secret = access_key['secret'] access_key_secret = access_key['secret']
self.access_key = self.access_key_class( self.access_key = self.access_key_class(
app=self.app, id=access_key_id, secret=access_key_secret id=access_key_id, secret=access_key_secret
) )
self.access_key.set_app(self.app)
logger.info('Register app success: %s' % access_key_id,) logger.info('Register app success: %s' % access_key_id,)
elif resp.status_code == 400: elif resp.status_code == 409:
msg = '{} exist already, register failed'.format(self.app.name) msg = '{} exist already, register failed'.format(self.app.name)
logging.error(msg) logging.error(msg)
sys.exit() sys.exit()
else: else:
logging.error('Register terminal {} failed unknown'.format(self.app.name)) logging.error('Register terminal {} failed unknown: {}'.format(self.app.name, resp.json()))
sys.exit() sys.exit()
def wait_util_accept(self):
while True:
if self.heatbeat() is None:
time.sleep(1)
else:
break
def heatbeat(self): def heatbeat(self):
"""和Jumpserver维持心跳, 当Terminal断线后,jumpserver可以知晓 """和Jumpserver维持心跳, 当Terminal断线后,jumpserver可以知晓
Todo: Jumpserver发送的任务也随heatbeat返回, 并执行,如 断开某用户 Todo: Jumpserver发送的任务也随heatbeat返回, 并执行,如 断开某用户
""" """
r, content = self.requests.post('terminal-heatbeat', use_auth=True) try:
if r.status_code == 201: resp = self.requests.post('terminal-heatbeat', use_auth=True)
return content except (ResponseError, RequestError):
return None
if resp.status_code == 201:
return True
else: else:
return None return None
...@@ -224,9 +227,9 @@ class AppService: ...@@ -224,9 +227,9 @@ class AppService:
'asset_id': asset_id, 'asset_id': asset_id,
'system_user_id': system_user_id, 'system_user_id': system_user_id,
} }
r, content = self.get('validate-user-asset-permission', r, content = self.requests.get(
use_auth=True, 'validate-user-asset-permission', use_auth=True, params=params
params=params) )
if r.status_code == 200: if r.status_code == 200:
return True return True
else: else:
...@@ -234,7 +237,7 @@ class AppService: ...@@ -234,7 +237,7 @@ class AppService:
def get_system_user_auth_info(self, system_user): def get_system_user_auth_info(self, system_user):
"""获取系统用户的认证信息: 密码, ssh私钥""" """获取系统用户的认证信息: 密码, ssh私钥"""
r, content = self.get('system-user-auth-info', pk=system_user['id']) r, content = self.requests.get('system-user-auth-info', pk=system_user['id'])
if r.status_code == 200: if r.status_code == 200:
password = content['password'] or '' password = content['password'] or ''
private_key_string = content['private_key'] or '' private_key_string = content['private_key'] or ''
...@@ -275,7 +278,7 @@ class AppService: ...@@ -275,7 +278,7 @@ class AppService:
data['date_start'] = timestamp_to_datetime_str(data['date_start']) data['date_start'] = timestamp_to_datetime_str(data['date_start'])
data['is_failed'] = 1 if data.get('is_failed') else 0 data['is_failed'] = 1 if data.get('is_failed') else 0
r, content = self.post('send-proxy-log', data=data, use_auth=True) r, content = self.requests.post('send-proxy-log', data=data, use_auth=True)
if r.status_code != 201: if r.status_code != 201:
logging.warning('Send proxy log failed: %s' % content) logging.warning('Send proxy log failed: %s' % content)
return None return None
...@@ -296,7 +299,7 @@ class AppService: ...@@ -296,7 +299,7 @@ class AppService:
data['is_failed'] = 1 if data.get('is_failed') else 0 data['is_failed'] = 1 if data.get('is_failed') else 0
data['is_finished'] = 1 data['is_finished'] = 1
proxy_log_id = data.get('proxy_log_id') or 0 proxy_log_id = data.get('proxy_log_id') or 0
r, content = self.patch('finish-proxy-log', pk=proxy_log_id, data=data) r, content = self.requests.patch('finish-proxy-log', pk=proxy_log_id, data=data)
if r.status_code != 200: if r.status_code != 200:
logging.warning('Finish proxy log failed: %s' % proxy_log_id) logging.warning('Finish proxy log failed: %s' % proxy_log_id)
...@@ -327,7 +330,7 @@ class AppService: ...@@ -327,7 +330,7 @@ class AppService:
output = d['output'].encode('utf-8', 'ignore') output = d['output'].encode('utf-8', 'ignore')
d['output'] = base64.b64encode(output).decode("utf-8") d['output'] = base64.b64encode(output).decode("utf-8")
result, content = self.post('send-command-log', data=data) result, content = self.requests.post('send-command-log', data=data)
if result.status_code != 201: if result.status_code != 201:
logging.warning('Send command log failed: %s' % content) logging.warning('Send command log failed: %s' % content)
return False return False
...@@ -349,7 +352,7 @@ class AppService: ...@@ -349,7 +352,7 @@ class AppService:
if d.get('output') and isinstance(d['output'], str): if d.get('output') and isinstance(d['output'], str):
d['output'] = d['output'].encode('utf-8') d['output'] = d['output'].encode('utf-8')
d['output'] = base64.b64encode(d['output']) d['output'] = base64.b64encode(d['output'])
result, content = self.post('send-record-log', data=data) result, content = self.requests.post('send-record-log', data=data)
if result.status_code != 201: if result.status_code != 201:
logging.warning('Send record log failed: %s' % content) logging.warning('Send record log failed: %s' % content)
return False return False
...@@ -371,27 +374,6 @@ class AppService: ...@@ -371,27 +374,6 @@ class AppService:
# user = user_service.is_authenticated() # user = user_service.is_authenticated()
# return user # return user
def login(self, data):
"""用户登录Terminal时需要向Jumpserver进行认证, 登陆成功后返回用户和token
data = {
'username': 'admin',
'password': 'admin',
'public_key': 'public key string',
'login_type': 'ST', # (('ST', 'SSH Terminal'),
# ('WT', 'Web Terminal'))
'remote_addr': '2.2.2.2', # User ip address not app address
}
"""
r, content = self.post('user-auth', data=data, use_auth=False)
if r.status_code == 200:
self.token = content['token']
self.user = content['user']
self.auth(self.token)
return self.user, self.token
else:
return None, None
@cached(TTLCache(maxsize=100, ttl=60)) @cached(TTLCache(maxsize=100, ttl=60))
def get_user_assets(self, user): def get_user_assets(self, user):
"""获取用户被授权的资产列表 """获取用户被授权的资产列表
...@@ -399,7 +381,7 @@ class AppService: ...@@ -399,7 +381,7 @@ class AppService:
'system_users_granted': [{'id': 1, 'username': 'x',..}] 'system_users_granted': [{'id': 1, 'username': 'x',..}]
] ]
""" """
r, content = self.get('user-assets', pk=user['id'], use_auth=True) r, content = self.requests.get('user-assets', pk=user['id'], use_auth=True)
if r.status_code == 200: if r.status_code == 200:
assets = content assets = content
else: else:
...@@ -416,7 +398,7 @@ class AppService: ...@@ -416,7 +398,7 @@ class AppService:
"""获取用户授权的资产组列表 """获取用户授权的资产组列表
[{'name': 'x', 'comment': 'x', 'assets_amount': 2}, ..] [{'name': 'x', 'comment': 'x', 'assets_amount': 2}, ..]
""" """
r, content = self.get('user-asset-groups', pk=user['id'], uassetsse_auth=True) r, content = self.requests.get('user-asset-groups', pk=user['id'], uassetsse_auth=True)
if r.status_code == 200: if r.status_code == 200:
asset_groups = content asset_groups = content
else: else:
...@@ -429,7 +411,7 @@ class AppService: ...@@ -429,7 +411,7 @@ class AppService:
"""获取用户授权的资产组列表及下面的资产 """获取用户授权的资产组列表及下面的资产
[{'name': 'x', 'comment': 'x', 'assets': []}, ..] [{'name': 'x', 'comment': 'x', 'assets': []}, ..]
""" """
r, content = self.get('user-asset-groups-assets', pk=user['id'], use_auth=True) r, content = self.requests.get('user-asset-groups-assets', pk=user['id'], use_auth=True)
if r.status_code == 200: if r.status_code == 200:
asset_groups_assets = content asset_groups_assets = content
else: else:
...@@ -443,7 +425,7 @@ class AppService: ...@@ -443,7 +425,7 @@ class AppService:
:param asset_group_id: 资产组id :param asset_group_id: 资产组id
""" """
r, content = self.get('assets-of-group', use_auth=True, r, content = self.requests.get('assets-of-group', use_auth=True,
pk=asset_group_id) pk=asset_group_id)
if r.status_code == 200: if r.status_code == 200:
assets = content assets = content
......
...@@ -91,7 +91,8 @@ def content_md5(data): ...@@ -91,7 +91,8 @@ def content_md5(data):
""" """
if isinstance(data, str): if isinstance(data, str):
data = hashlib.md5(data.encode('utf-8')) data = hashlib.md5(data.encode('utf-8'))
return base64.b64encode(data.digest()) value = base64.b64encode(data.digest())
return value.decode('utf-8')
_STRPTIME_LOCK = threading.Lock() _STRPTIME_LOCK = threading.Lock()
...@@ -126,7 +127,7 @@ def iso8601_to_unixtime(time_string): ...@@ -126,7 +127,7 @@ def iso8601_to_unixtime(time_string):
def make_signature(access_key_secret, date=None): def make_signature(access_key_secret, date=None):
if isinstance(date, bytes): if isinstance(date, bytes):
date = date.decode("utf-8") date = bytes.decode(date)
if isinstance(date, int): if isinstance(date, int):
date_gmt = http_date(date) date_gmt = http_date(date)
elif date is None: elif date is None:
......
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