Unverified Commit 2983f06a authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #145 from jumpserver/dev

Dev
parents 1233b998 f510d762
...@@ -56,6 +56,7 @@ class Coco: ...@@ -56,6 +56,7 @@ class Coco:
return self._task_handler return self._task_handler
@staticmethod @staticmethod
@ignore_error
def load_extra_conf_from_server(): def load_extra_conf_from_server():
configs = app_service.load_config_from_server() configs = app_service.load_config_from_server()
logger.debug("Loading config from server: {}".format( logger.debug("Loading config from server: {}".format(
...@@ -63,8 +64,17 @@ class Coco: ...@@ -63,8 +64,17 @@ class Coco:
)) ))
config.update(configs) config.update(configs)
def keep_load_extra_conf(self):
def func():
while True:
self.load_extra_conf_from_server()
time.sleep(60*10)
thread = threading.Thread(target=func)
thread.start()
def bootstrap(self): def bootstrap(self):
self.load_extra_conf_from_server() self.load_extra_conf_from_server()
self.keep_load_extra_conf()
self.keep_heartbeat() self.keep_heartbeat()
self.monitor_sessions() self.monitor_sessions()
self.monitor_sessions_replay() self.monitor_sessions_replay()
......
...@@ -92,8 +92,9 @@ class Config(dict): ...@@ -92,8 +92,9 @@ class Config(dict):
""" """
def __init__(self, root_path, defaults=None): def __init__(self, root_path, defaults=None):
dict.__init__(self, defaults or {}) self.defaults = defaults or {}
self.root_path = root_path self.root_path = root_path
super().__init__({})
def from_envvar(self, variable_name, silent=False): def from_envvar(self, variable_name, silent=False):
"""Loads a configuration from an environment variable pointing to """Loads a configuration from an environment variable pointing to
...@@ -269,6 +270,21 @@ class Config(dict): ...@@ -269,6 +270,21 @@ class Config(dict):
rv[key] = v rv[key] = v
return rv return rv
def __getitem__(self, item):
try:
value = super().__getitem__(item)
except KeyError:
value = None
if value is not None:
return value
value = os.environ.get(item, None)
if value is not None:
return value
return self.defaults.get(item)
def __getattr__(self, item):
return self.__getitem__(item)
def __repr__(self): def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self)) return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
...@@ -302,6 +318,7 @@ default_config = { ...@@ -302,6 +318,7 @@ default_config = {
'REPLAY_STORAGE': {'TYPE': 'server'}, 'REPLAY_STORAGE': {'TYPE': 'server'},
'LANGUAGE_CODE': 'zh', 'LANGUAGE_CODE': 'zh',
'SECURITY_MAX_IDLE_TIME': 60, 'SECURITY_MAX_IDLE_TIME': 60,
'ASSET_LIST_PAGE_SIZE': 'auto',
} }
config = Config(root_path, default_config) config = Config(root_path, default_config)
......
...@@ -32,47 +32,51 @@ class InteractiveServer: ...@@ -32,47 +32,51 @@ class InteractiveServer:
def __init__(self, client): def __init__(self, client):
self.client = client self.client = client
self.closed = False self.closed = False
self._search_result = None self._results = None
self.nodes = None self.nodes = None
self.offset = 0 self.offset = 0
self.limit = 100 self.limit = 100
self.assets_list = [] self.assets = []
self.finish = False self.finish = False
self.page = 1 self.page = 1
self.total_assets = 0 self.total_assets = 0 # 用户被授权的所有资产
self.total_count = 0 # 分页展示中用来存放数目总条数 self.total_count = 0 # 分页展示中的资产总数量
self.nodes_tree = None # 授权节点树 self.node_tree = None # 授权节点树
self.get_user_assets_paging_async() self.get_user_assets_async()
self.get_user_nodes_async() self.get_user_nodes_async()
@property @property
def page_size(self): def page_size(self):
return self.client.request.meta['height'] - 8 _page_size = config['ASSET_LIST_PAGE_SIZE']
if _page_size.isdigit():
return int(_page_size)
elif _page_size == 'all':
return self.total_count
else:
return self.client.request.meta['height'] - 8
@property
def total_pages(self):
return math.ceil(self.total_count/self.page_size)
@property
def need_paging(self):
return config['ASSET_LIST_PAGE_SIZE'] != 'all'
@property @property
def search_result(self): def results(self):
if self._search_result: if self._results:
return self._search_result return self._results
else: else:
return [] return []
@search_result.setter @results.setter
def search_result(self, value): def results(self, value):
if not value: self._results = value
self._search_result = value
return
value = self.filter_system_users(value)
self._search_result = value
def display_logo(self): #
logo_path = os.path.join(config['ROOT_PATH'], "logo.txt") # Display banner
if not os.path.isfile(logo_path): #
return
with open(logo_path, 'rb') as f:
for i in f:
if i.decode('utf-8').startswith('#'):
continue
self.client.send(i.decode('utf-8').replace('\n', '\r\n'))
def display_banner(self): def display_banner(self):
self.client.send(char.CLEAR_CHAR) self.client.send(char.CLEAR_CHAR)
...@@ -99,15 +103,25 @@ class InteractiveServer: ...@@ -99,15 +103,25 @@ class InteractiveServer:
T='\t', R='\r\n\r' T='\t', R='\r\n\r'
)) ))
def display_logo(self):
logo_path = os.path.join(config['ROOT_PATH'], "logo.txt")
if not os.path.isfile(logo_path):
return
with open(logo_path, 'rb') as f:
for i in f:
if i.decode('utf-8').startswith('#'):
continue
self.client.send(i.decode('utf-8').replace('\n', '\r\n'))
def dispatch(self, opt): def dispatch(self, opt):
if opt is None: if opt is None:
return self._sentinel return self._sentinel
elif opt.startswith("/"): elif opt.startswith("/"):
self.search_and_display(opt.lstrip("/")) self.search_and_display_assets(opt.lstrip("/"))
elif opt in ['p', 'P', '']: elif opt in ['p', 'P', '']:
self.display_assets() self.display_assets()
elif opt in ['g', 'G']: elif opt in ['g', 'G']:
self.display_nodes_tree() self.display_nodes_as_tree()
elif opt.startswith("g") and opt.lstrip("g").isdigit(): elif opt.startswith("g") and opt.lstrip("g").isdigit():
self.display_node_assets(int(opt.lstrip("g"))) self.display_node_assets(int(opt.lstrip("g")))
elif opt in ['q', 'Q', 'exit', 'quit']: elif opt in ['q', 'Q', 'exit', 'quit']:
...@@ -121,98 +135,172 @@ class InteractiveServer: ...@@ -121,98 +135,172 @@ class InteractiveServer:
elif opt in ['h', 'H']: elif opt in ['h', 'H']:
self.display_banner() self.display_banner()
else: else:
self.search_and_proxy(opt) self.search_and_proxy_assets(opt)
#
# Search assets
#
def search_and_display_assets(self, q):
assets = self.search_assets(q)
self.display_assets(assets)
def search_and_proxy_assets(self, opt):
assets = self.search_assets(opt)
if assets and len(assets) == 1:
asset = assets[0]
if asset.protocol == "rdp" \
or asset.platform.lower().startswith("windows"):
self.client.send(warning(
_("Terminal does not support login rdp, "
"please use web terminal to access"))
)
return
self.proxy(asset)
else:
self.display_assets(assets)
def refresh_assets_nodes(self): def refresh_assets_nodes(self):
self.get_user_assets_paging_async() self.get_user_assets_async()
self.get_user_nodes_async() self.get_user_nodes_async()
def search_assets(self, q): def search_assets(self, q):
if not self.finish: if self.finish:
assets = app_service.get_search_user_granted_assets(self.client.user, q) assets = self.search_assets_from_local(q)
return assets else:
assets = self.assets_list assets = self.search_assets_from_core(q)
return assets
def search_assets_from_core(self, q):
assets = app_service.get_search_user_granted_assets(self.client.user, q)
assets = self.filter_system_users(assets)
return assets
def search_assets_from_local(self, q):
result = [] result = []
# 所有的 # 所有的
if q in ('', None): if q in ('', None):
result = assets result = self.assets
# 全匹配到则直接返回全匹配的 # 全匹配到则直接返回全匹配的
if len(result) == 0: if len(result) == 0:
_result = [asset for asset in assets _result = [asset for asset in self.assets if is_obj_attr_eq(asset, q)]
if is_obj_attr_eq(asset, q)]
if len(_result) == 1: if len(_result) == 1:
result = _result result = _result
# 最后模糊匹配 # 最后模糊匹配
if len(result) == 0: if len(result) == 0:
result = [asset for asset in assets result = [asset for asset in self.assets if is_obj_attr_has(asset, q)]
if is_obj_attr_has(asset, q)]
return result return result
def display_assets(self): #
""" # Display assets
Display user all assets #
:return:
"""
self.display_result_paging(self.assets_list)
def display_nodes(self): def display_assets(self, assets=None):
if self.nodes is None: if assets is None:
self.get_user_nodes() while not self.assets and not self.finish:
time.sleep(0.2)
assets = self.assets
self.display_assets_paging(assets)
def display_assets_paging(self, assets):
if len(self.nodes) == 0: if len(assets) == 0:
self.client.send(warning(_("No"))) self.client.send(wr(_("No Assets"), before=0))
return return
id_length = max(len(str(len(self.nodes))), 5) self.total_count = self.total_assets if assets is self.assets else len(assets)
name_length = item_max_length(self.nodes, 15, key=lambda x: x.name)
amount_length = item_max_length(self.nodes, 10, key=lambda x: x.assets_amount)
size_list = [id_length, name_length, amount_length]
fake_data = ['ID', _("Name"), _("Assets")]
self.client.send(wr(title(format_with_zh(size_list, *fake_data)))) action = None
for index, node in enumerate(self.nodes, 1): gen = self._page_generator(assets)
data = [index, node.name, node.assets_amount] while True:
self.client.send(wr(format_with_zh(size_list, *data))) try:
self.client.send(wr(_("Total: {}").format(len(self.nodes)), before=1)) page, _assets = gen.send(action)
except StopIteration as e:
if None not in e.value:
page, _assets = e.value
self.display_a_page_assets(page, _assets)
break
else:
self.display_a_page_assets(page, _assets)
self.display_page_bottom_prompt()
action = self.get_user_action()
def display_nodes_tree(self): def _page_generator(self, assets):
if self.nodes is None: start, page = 0, 1
self.get_user_nodes() while True:
_assets = assets[start:start+self.page_size]
# 等待加载
if (assets is self.assets) and (not self.finish) and (not self.need_paging):
time.sleep(1)
continue
# 最后一页
elif _assets and (page == self.total_pages) and (
assets is not self.assets
or (assets is self.assets and self.finish)):
return page, _assets
# 执行动作
else:
action = yield page, _assets
if not self.nodes: # 退出
self.client.send(wr(_('No Nodes'), before=0)) if action == BACK:
return return None, None
# 不分页, 不对页码和下标做更改
elif not self.need_paging:
continue
# 上一页
elif action == PAGE_UP:
if page <= 1:
page = 1
start = 0
else:
page -= 1
start -= self.page_size
# 下一页
else:
page += 1
start += len(_assets)
self.nodes_tree.show(key=lambda node: node.identifier) def display_a_page_assets(self, page, assets):
self.client.send(wr(title(_("Node: [ ID.Name(Asset amount) ]")), before=0)) self.client.send(char.CLEAR_CHAR)
self.client.send(wr(self.nodes_tree._reader.replace('\n', '\r\n'), before=0)) self.page = page
prompt = _("Tips: Enter g+NodeID to display the host under the node, such as g1") self.results = assets
self.client.send(wr(title(prompt), before=1)) self.display_results()
def display_node_assets(self, _id): def display_page_bottom_prompt(self):
if self.nodes is None: self.client.send(wr(_('Tips: Enter the asset ID and log directly into the asset.'), before=1))
self.get_user_nodes() prompt_page_up = _("Page up: P/p")
if _id > len(self.nodes) or _id <= 0: prompt_page_down = _("Page down: Enter|N/n")
msg = wr(warning(_("There is no matched node, please re-enter"))) prompt_back = _("BACK: b/q")
self.client.send(msg) prompts = [prompt_page_up, prompt_page_down, prompt_back]
self.display_nodes_tree() prompt = '\t'.join(prompts)
return self.client.send(wr(prompt, before=1))
assets = self.nodes[_id - 1].assets_granted def get_user_action(self):
self.display_result_paging(assets) opt = net_input(self.client, prompt=':', only_one_char=True)
print(opt)
if opt in ('p', 'P'):
return PAGE_UP
elif opt in ('b', 'q'):
return BACK
elif opt.isdigit() and self.results and 0 < int(opt) <= len(self.results):
self.proxy(self.results[int(opt)-1])
return BACK
else:
return PAGE_DOWN
def display_search_result(self): def display_results(self):
sort_by = config["ASSET_LIST_SORT_BY"] sort_by = config["ASSET_LIST_SORT_BY"]
self.search_result = sort_assets(self.search_result, sort_by) self.results = sort_assets(self.results, sort_by)
fake_data = [_("ID"), _("Hostname"), _("IP"), _("LoginAs")] fake_data = [_("ID"), _("Hostname"), _("IP"), _("LoginAs")]
id_length = max(len(str(len(self.search_result))), 4) id_length = max(len(str(len(self.results))), 4)
hostname_length = item_max_length(self.search_result, 15, hostname_length = item_max_length(self.results, 15,
key=lambda x: x.hostname) key=lambda x: x.hostname)
sysuser_length = item_max_length(self.search_result, sysuser_length = item_max_length(self.results,
key=lambda x: x.system_users_name_list) key=lambda x: x.system_users_name_list)
size_list = [id_length, hostname_length, 16, sysuser_length] size_list = [id_length, hostname_length, 16, sysuser_length]
header_without_comment = format_with_zh(size_list, *fake_data) header_without_comment = format_with_zh(size_list, *fake_data)
...@@ -224,43 +312,110 @@ class InteractiveServer: ...@@ -224,43 +312,110 @@ class InteractiveServer:
size_list.append(comment_length) size_list.append(comment_length)
fake_data.append(_("Comment")) fake_data.append(_("Comment"))
self.client.send(wr(title(format_with_zh(size_list, *fake_data)))) self.client.send(wr(title(format_with_zh(size_list, *fake_data))))
for index, asset in enumerate(self.search_result, 1): for index, asset in enumerate(self.results, 1):
data = [ data = [
index, asset.hostname, asset.ip, index, asset.hostname, asset.ip,
asset.system_users_name_list, asset.comment asset.system_users_name_list, asset.comment
] ]
self.client.send(wr(format_with_zh(size_list, *data))) self.client.send(wr(format_with_zh(size_list, *data)))
total_page = math.ceil(self.total_count/self.page_size)
self.client.send(wr(title(_("Page: {}, Count: {}, Total Page: {}, Total Count: {}").format( self.client.send(wr(title(_("Page: {}, Count: {}, Total Page: {}, Total Count: {}").format(
self.page, len(self.search_result), total_page, self.total_count)), before=1) self.page, len(self.results), self.total_pages, self.total_count)), before=1)
) )
def search_and_display(self, q): #
assets = self.search_assets(q) # Get assets
self.display_result_paging(assets) #
def get_user_nodes(self): def get_user_assets_async(self):
self.nodes = app_service.get_user_asset_groups(self.client.user) if self.need_paging:
self.sort_nodes() thread = threading.Thread(target=self.get_user_assets_paging)
self.construct_nodes_tree() else:
thread = threading.Thread(target=self.get_user_assets_direct)
thread.start()
def get_user_assets_direct(self):
assets = app_service.get_user_assets(self.client.user)
assets = self.filter_system_users(assets)
self.assets = assets
self.total_assets = len(assets)
self.finish = True
def get_user_assets_paging(self):
while not self.closed:
assets, total = app_service.get_user_assets_paging(
self.client.user, offset=self.offset, limit=self.limit
)
if not assets:
logger.info('Get user assets paging async finished.')
self.finish = True
break
def sort_nodes(self): logger.info('Get user assets paging async: {}'.format(len(assets)))
self.nodes = sorted(self.nodes, key=lambda node: node.key) assets = self.filter_system_users(assets)
self.total_assets = total
self.assets.extend(assets)
self.offset += self.limit
#
# Nodes
#
def get_user_nodes_async(self):
thread = threading.Thread(target=self.get_user_nodes)
thread.start()
def construct_nodes_tree(self): def get_user_nodes(self):
self.nodes_tree = Tree() nodes = app_service.get_user_asset_groups(self.client.user)
nodes = sorted(nodes, key=lambda node: node.key)
self.nodes = self.filter_system_users_of_assets_under_nodes(nodes)
self._construct_node_tree()
def filter_system_users_of_assets_under_nodes(self, nodes):
for node in nodes:
node.assets_granted = self.filter_system_users(node.assets_granted)
return nodes
def _construct_node_tree(self):
self.node_tree = Tree()
root = 'ROOT_ALL_ORG_NODE' root = 'ROOT_ALL_ORG_NODE'
self.nodes_tree.create_node(tag='', identifier=root, parent=None) self.node_tree.create_node(tag='', identifier=root, parent=None)
for index, node in enumerate(self.nodes): for index, node in enumerate(self.nodes):
tag = "{}.{}({})".format(index+1, node.name, node.assets_amount) tag = "{}.{}({})".format(index+1, node.name, node.assets_amount)
key = node.key key = node.key
parent_key = key[:node.key.rfind(':')] or root parent_key = key[:node.key.rfind(':')] or root
self.nodes_tree.create_node(tag=tag, identifier=key, data=node, parent=parent_key) self.node_tree.create_node(tag=tag, identifier=key, data=node, parent=parent_key)
def get_user_nodes_async(self): def display_nodes_as_tree(self):
thread = threading.Thread(target=self.get_user_nodes) if self.nodes is None:
thread.start() self.get_user_nodes()
if not self.nodes:
self.client.send(wr(_('No Nodes'), before=0))
return
self.node_tree.show(key=lambda node: node.identifier)
self.client.send(wr(title(_("Node: [ ID.Name(Asset amount) ]")), before=0))
self.client.send(wr(self.node_tree._reader.replace('\n', '\r\n'), before=0))
prompt = _("Tips: Enter g+NodeID to display the host under the node, such as g1")
self.client.send(wr(title(prompt), before=1))
def display_node_assets(self, _id):
if self.nodes is None:
self.get_user_nodes()
if _id > len(self.nodes) or _id <= 0:
msg = wr(warning(_("There is no matched node, please re-enter")))
self.client.send(msg)
self.display_nodes_as_tree()
return
assets = self.nodes[_id-1].assets_granted
self.display_assets(assets)
#
# System users
#
@staticmethod @staticmethod
def filter_system_users(assets): def filter_system_users(assets):
...@@ -273,26 +428,6 @@ class InteractiveServer: ...@@ -273,26 +428,6 @@ class InteractiveServer:
asset.system_users_granted = system_users_cleaned asset.system_users_granted = system_users_cleaned
return assets return assets
def get_user_assets_paging(self):
while not self.closed:
assets, total = app_service.get_user_assets_paging(
self.client.user, offset=self.offset, limit=self.limit
)
logger.info('Get user assets paging async: {}'.format(len(assets)))
if not assets:
logger.info('Get user assets paging async finished.')
self.finish = True
return
if not self.total_assets:
self.total_assets = total
self.total_count = total
self.assets_list.extend(assets)
self.offset += self.limit
def get_user_assets_paging_async(self):
thread = threading.Thread(target=self.get_user_assets_paging)
thread.start()
def choose_system_user(self, system_users): def choose_system_user(self, system_users):
if len(system_users) == 1: if len(system_users) == 1:
return system_users[0] return system_users[0]
...@@ -316,124 +451,9 @@ class InteractiveServer: ...@@ -316,124 +451,9 @@ class InteractiveServer:
for index, system_user in enumerate(system_users): for index, system_user in enumerate(system_users):
self.client.send(wr("{} {}".format(index, system_user.name))) self.client.send(wr("{} {}".format(index, system_user.name)))
def search_and_proxy(self, opt): #
assets = self.search_assets(opt) # Proxy
if assets and len(assets) == 1: #
asset = assets[0]
self.search_result = None
if asset.protocol == "rdp" or asset.platform.lower().startswith("windows"):
self.client.send(warning(
_("Terminal does not support login rdp, "
"please use web terminal to access"))
)
return
self.proxy(asset)
else:
self.display_result_paging(assets)
def display_result_paging(self, result_list):
if result_list is self.assets_list:
self.total_count = self.total_assets
else:
if len(result_list) == 0:
return
self.total_count = len(result_list)
action = PAGE_DOWN
gen_result = self.get_result_page_down_or_up(result_list)
while True:
try:
page, result = gen_result.send(action)
except TypeError:
try:
page, result = next(gen_result)
except StopIteration:
logger.info('No Assets')
# self.display_banner()
self.client.send(wr(_("No Assets"), before=0))
return None
except StopIteration:
logger.info('Back display result paging.')
# self.display_banner()
return None
self.display_result_of_page(page, result)
action = self.get_user_action()
def get_result_page_down_or_up(self, result_list):
left = 0
page = 1
page_up_size = 0 # 记录上一页大小
while True:
right = left + self.page_size
result = result_list[left:right]
if not result and (result_list is self.assets_list) and self.finish and self.total_assets == 0:
# 无授权资产
return None, None
elif not result and (result_list is self.assets_list) and self.finish:
# 上一页是最后一页
left -= page_up_size
page -= 1
continue
elif not result and (result_list is self.assets_list) and not self.finish:
# 还有下一页(暂时没有加载完),需要等待
time.sleep(1)
continue
elif not result and (result_list is not self.assets_list):
# 上一页是最后一页
left -= page_up_size
page -= 1
continue
else:
# 其他4中情况,返回assets
action = yield (page, result)
if action == BACK:
return None, None
elif action == PAGE_UP:
if page <= 1:
# 已经是第一页了
page = 1
left = 0
else:
page -= 1
left -= self.page_size
else:
# PAGE_DOWN
page += 1
left += len(result)
page_up_size = len(result)
def display_result_of_page(self, page, result):
self.client.send(char.CLEAR_CHAR)
self.page = page
self.search_result = result
self.display_search_result()
self.display_prompt_of_page()
def display_prompt_of_page(self):
self.client.send(wr(_('Tips: Enter the asset ID and log directly into the asset.'), before=1))
prompt_page_up = _("Page up: P/p")
prompt_page_down = _("Page down: Enter|N/n")
prompt_back = _("BACK: b/q")
prompts = [prompt_page_up, prompt_page_down, prompt_back]
prompt = '\t'.join(prompts)
self.client.send(wr(prompt, before=1))
def get_user_action(self):
opt = net_input(self.client, prompt=':')
if opt in ('p', 'P'):
return PAGE_UP
elif opt in ('b', 'q'):
return BACK
elif opt.isdigit() and self.search_result and 0 < int(opt) <= len(self.search_result):
self.proxy(self.search_result[int(opt)-1])
return BACK
else:
# PAGE_DOWN
return PAGE_DOWN
def proxy(self, asset): def proxy(self, asset):
system_user = self.choose_system_user(asset.system_users_granted) system_user = self.choose_system_user(asset.system_users_granted)
...@@ -443,6 +463,10 @@ class InteractiveServer: ...@@ -443,6 +463,10 @@ class InteractiveServer:
forwarder = ProxyServer(self.client, asset, system_user) forwarder = ProxyServer(self.client, asset, system_user)
forwarder.proxy() forwarder.proxy()
#
# Entrance
#
def interact(self): def interact(self):
self.display_banner() self.display_banner()
while not self.closed: while not self.closed:
...@@ -456,15 +480,16 @@ class InteractiveServer: ...@@ -456,15 +480,16 @@ class InteractiveServer:
break break
self.close() self.close()
def interact_async(self):
thread = threading.Thread(target=self.interact)
thread.daemon = True
thread.start()
def close(self): def close(self):
logger.debug("Interactive server server close: {}".format(self)) logger.debug("Interactive server server close: {}".format(self))
self.closed = True self.closed = True
# current_app.remove_client(self.client) # current_app.remove_client(self.client)
def interact_async(self):
# 目前没用
thread = threading.Thread(target=self.interact)
thread.daemon = True
thread.start()
# def __del__(self): # def __del__(self):
# print("GC: Interactive class been gc") # print("GC: Interactive class been gc")
...@@ -10,7 +10,7 @@ from .config import config as app_config ...@@ -10,7 +10,7 @@ from .config import config as app_config
def create_logger(): def create_logger():
level = app_config['LOG_LEVEL'] level = app_config['LOG_LEVEL']
log_dir = app_config.get('LOG_DIR') log_dir = app_config['LOG_DIR']
log_path = os.path.join(log_dir, 'coco.log') log_path = os.path.join(log_dir, 'coco.log')
main_setting = { main_setting = {
'handlers': ['console', 'file'], 'handlers': ['console', 'file'],
......
...@@ -360,6 +360,11 @@ class TelnetServer(BaseServer): ...@@ -360,6 +360,11 @@ class TelnetServer(BaseServer):
self.system_user = system_user self.system_user = system_user
super(TelnetServer, self).__init__(chan=sock) super(TelnetServer, self).__init__(chan=sock)
@property
def closed(self):
""" self.chan: socket object """
return getattr(self.chan, '_closed', False)
class Server(BaseServer): class Server(BaseServer):
""" """
......
...@@ -63,6 +63,7 @@ class SSHServer: ...@@ -63,6 +63,7 @@ class SSHServer:
return connection return connection
def handle_connection(self, sock, addr): def handle_connection(self, sock, addr):
logger.debug("Handle new connection from: {}".format(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()
...@@ -77,7 +78,7 @@ class SSHServer: ...@@ -77,7 +78,7 @@ class SSHServer:
server = SSHInterface(connection) server = SSHInterface(connection)
try: try:
transport.start_server(server=server) transport.start_server(server=server)
except paramiko.SSHException: except (paramiko.SSHException, socket.timeout):
logger.warning("SSH negotiation failed") logger.warning("SSH negotiation failed")
return return
except EOFError as e: except EOFError as e:
......
...@@ -294,7 +294,7 @@ def get_logger(file_name): ...@@ -294,7 +294,7 @@ def get_logger(file_name):
return logging.getLogger('coco.'+file_name) return logging.getLogger('coco.'+file_name)
def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0): def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0, only_one_char=False):
"""实现了一个ssh input, 提示用户输入, 获取并返回 """实现了一个ssh input, 提示用户输入, 获取并返回
:return user input string :return user input string
...@@ -303,6 +303,10 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0): ...@@ -303,6 +303,10 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0):
parser = TtyIOParser() parser = TtyIOParser()
client.send(wrap_with_line_feed(prompt, before=before, after=after)) client.send(wrap_with_line_feed(prompt, before=before, after=after))
if only_one_char:
data = client.recv(1)
return data.decode()
while True: while True:
data = client.recv(10) data = client.recv(10)
if len(data) == 0: if len(data) == 0:
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-31 11:49+0800\n" "POT-Creation-Date: 2018-12-18 20:03+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n" "PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n" "Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/en/LC\n" "Language-Team: Language locale/en/LC\n"
...@@ -16,11 +16,11 @@ msgstr "" ...@@ -16,11 +16,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:135 #: coco/app.py:145
msgid "Connect idle more than {} minutes, disconnect" msgid "Connect idle more than {} minutes, disconnect"
msgstr "" msgstr ""
#: coco/interactive.py:80 #: coco/interactive.py:84
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"\n" "\n"
...@@ -28,136 +28,127 @@ msgid "" ...@@ -28,136 +28,127 @@ msgid ""
"{end}{R}{R}" "{end}{R}{R}"
msgstr "" msgstr ""
#: coco/interactive.py:82 #: coco/interactive.py:86
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, " "{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, "
"Comment{end} to search login(if unique).{R}" "Comment{end} to search login(if unique).{R}"
msgstr "" msgstr ""
#: coco/interactive.py:83 #: coco/interactive.py:87
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} " "{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
"search, such as: /ip.{R}" "search, such as: /ip.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:84 #: coco/interactive.py:88
#, python-brace-format #, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}" msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:85 #: coco/interactive.py:89
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}" "{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:86 #: coco/interactive.py:90
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}5) Enter {green}g{end} + {green}NodeID{end} to display the host under the " "{T}5) Enter {green}g{end} + {green}NodeID{end} to display the host under the "
"node, such as g1.{R}" "node, such as g1.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:87 #: coco/interactive.py:91
#, python-brace-format #, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}" msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:88 #: coco/interactive.py:92
#, python-brace-format #, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}" msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:89 #: coco/interactive.py:93
#, python-brace-format #, python-brace-format
msgid "{T}8) Enter {green}r{end} to refresh your assets and nodes.{R}" msgid "{T}8) Enter {green}r{end} to refresh your assets and nodes.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:90 #: coco/interactive.py:94
#, python-brace-format #, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}" msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr "" msgstr ""
#: coco/interactive.py:159 #: coco/interactive.py:155
msgid "No" msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr ""
#: coco/interactive.py:166
msgid "Name"
msgstr "" msgstr ""
#: coco/interactive.py:166 #: coco/interactive.py:212
msgid "Assets" msgid "No Assets"
msgstr "" msgstr ""
#: coco/interactive.py:172 #: coco/interactive.py:275
msgid "Total: {}" msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr "" msgstr ""
#: coco/interactive.py:177 #: coco/interactive.py:276
msgid "Node [ ID.Name(Asset) ]" msgid "Page up: P/p"
msgstr "" msgstr ""
#: coco/interactive.py:179 #: coco/interactive.py:277
msgid "Enter g+NodeID to display the host under the node, such as g1." msgid "Page down: Enter|N/n"
msgstr "" msgstr ""
#: coco/interactive.py:186 #: coco/interactive.py:278
msgid "There is no matched node, please re-enter" msgid "BACK: b/q"
msgstr "" msgstr ""
#: coco/interactive.py:197 #: coco/interactive.py:299
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: coco/interactive.py:197 #: coco/interactive.py:299
msgid "Hostname" msgid "Hostname"
msgstr "" msgstr ""
#: coco/interactive.py:197 #: coco/interactive.py:299
msgid "IP" msgid "IP"
msgstr "" msgstr ""
#: coco/interactive.py:197 #: coco/interactive.py:299
msgid "LoginAs" msgid "LoginAs"
msgstr "" msgstr ""
#: coco/interactive.py:211 #: coco/interactive.py:313
msgid "Comment" msgid "Comment"
msgstr "" msgstr ""
#: coco/interactive.py:221 #: coco/interactive.py:322
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}" msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr "" msgstr ""
#: coco/interactive.py:296 #: coco/interactive.py:394
msgid "Select a login:: " msgid "No Nodes"
msgstr "" msgstr ""
#: coco/interactive.py:319 #: coco/interactive.py:398
msgid "" msgid "Node: [ ID.Name(Asset amount) ]"
"Terminal does not support login Windows, please use web terminal to access"
msgstr "" msgstr ""
#: coco/interactive.py:401 #: coco/interactive.py:400
msgid "Tips: Enter the asset ID and log directly into the asset." msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr "" msgstr ""
#: coco/interactive.py:402 #: coco/interactive.py:408
msgid "Page up: P/p" msgid "There is no matched node, please re-enter"
msgstr ""
#: coco/interactive.py:403
msgid "Page down: Enter|N/n"
msgstr "" msgstr ""
#: coco/interactive.py:404 #: coco/interactive.py:438
msgid "BACK: B/b" msgid "Select a login:: "
msgstr "" msgstr ""
#: coco/interactive.py:425 #: coco/interactive.py:461
msgid "No system user" msgid "No system user"
msgstr "" msgstr ""
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-31 11:49+0800\n" "POT-Creation-Date: 2018-12-18 20:04+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n" "PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n" "Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/zh\n" "Language-Team: Language locale/zh\n"
...@@ -16,11 +16,11 @@ msgstr "" ...@@ -16,11 +16,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:135 #: coco/app.py:145
msgid "Connect idle more than {} minutes, disconnect" msgid "Connect idle more than {} minutes, disconnect"
msgstr "空闲时间超过 {} 分钟,断开连接" msgstr "空闲时间超过 {} 分钟,断开连接"
#: coco/interactive.py:80 #: coco/interactive.py:84
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"\n" "\n"
...@@ -30,7 +30,7 @@ msgstr "" ...@@ -30,7 +30,7 @@ msgstr ""
"\n" "\n"
"{T}{T}{title} {user}, 欢迎使用Jumpserver开源跳板机系统 {end}{R}{R}" "{T}{T}{title} {user}, 欢迎使用Jumpserver开源跳板机系统 {end}{R}{R}"
#: coco/interactive.py:82 #: coco/interactive.py:86
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, " "{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, "
...@@ -39,7 +39,7 @@ msgstr "" ...@@ -39,7 +39,7 @@ msgstr ""
"{T}1) 输入 {green}ID{end} 直接登录 或 输入{green}部分 IP,主机名,备注{end} 进" "{T}1) 输入 {green}ID{end} 直接登录 或 输入{green}部分 IP,主机名,备注{end} 进"
"行搜索登录(如果唯一).{R}" "行搜索登录(如果唯一).{R}"
#: coco/interactive.py:83 #: coco/interactive.py:87
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} " "{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
...@@ -48,127 +48,120 @@ msgstr "" ...@@ -48,127 +48,120 @@ msgstr ""
"{T}2) 输入 {green}/{end} + {green}IP, 主机名{end} or {green}备注 {end}搜索. " "{T}2) 输入 {green}/{end} + {green}IP, 主机名{end} or {green}备注 {end}搜索. "
"如: /ip{R}" "如: /ip{R}"
#: coco/interactive.py:84 #: coco/interactive.py:88
#, python-brace-format #, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}" msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr "{T}3) 输入 {green}p{end} 显示您有权限的主机.{R}" msgstr "{T}3) 输入 {green}p{end} 显示您有权限的主机.{R}"
#: coco/interactive.py:85 #: coco/interactive.py:89
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}" "{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr "{T}4) 输入 {green}g{end} 显示您有权限的节点.{R}" msgstr "{T}4) 输入 {green}g{end} 显示您有权限的节点.{R}"
#: coco/interactive.py:86 #: coco/interactive.py:90
#, python-brace-format
msgid "" msgid ""
"{T}5) Enter {green}g{end} + {green}NodeID{end} to display the host under the " "{T}5) Enter {green}g{end} + {green}NodeID{end} to display the host under the "
"node, such as g1.{R}" "node, such as g1.{R}"
msgstr "{T}5) 输入 {green}g{end} + {green}节点ID{end} 显示节点下主机. 如: g1{R}" msgstr ""
"{T}5) 输入 {green}g{end} + {green}节点ID{end} 显示节点下主机. 如: g1{R}"
#: coco/interactive.py:87 #: coco/interactive.py:91
#, python-brace-format #, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}" msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr "{T}6) 输入 {green}s{end} 中/英文切换.{R}" msgstr "{T}6) 输入 {green}s{end} 中/英文切换.{R}"
#: coco/interactive.py:88 #: coco/interactive.py:92
#, python-brace-format #, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}" msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr "{T}7) 输入 {green}h{end} 帮助.{R}" msgstr "{T}7) 输入 {green}h{end} 帮助.{R}"
#: coco/interactive.py:89 #: coco/interactive.py:93
#, python-brace-format #, python-brace-format
msgid "{T}8) Enter {green}r{end} to refresh your assets and nodes.{R}" msgid "{T}8) Enter {green}r{end} to refresh your assets and nodes.{R}"
msgstr "{T}0) 输入 {green}r{end} 刷新最新的机器和节点信息.{R}" msgstr "{T}0) 输入 {green}r{end} 刷新最新的机器和节点信息.{R}"
#: coco/interactive.py:90 #: coco/interactive.py:94
#, python-brace-format #, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}" msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr "{T}0) 输入 {green}q{end} 退出.{R}" msgstr "{T}0) 输入 {green}q{end} 退出.{R}"
#: coco/interactive.py:159 #: coco/interactive.py:155
msgid "No" msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr "无" msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:166
msgid "Name"
msgstr "名称"
#: coco/interactive.py:166 #: coco/interactive.py:212
msgid "Assets" msgid "No Assets"
msgstr "资产" msgstr "没有资产"
#: coco/interactive.py:172 #: coco/interactive.py:275
msgid "Total: {}" msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr "总共: {}" msgstr "提示: 输入资产ID,直接登录资产."
#: coco/interactive.py:177 #: coco/interactive.py:276
msgid "Node: [ ID.Name(Asset amount) ]" msgid "Page up: P/p"
msgstr "节点: [ ID.名称(资产数量) ]" msgstr "上一页: P/p"
#: coco/interactive.py:179 #: coco/interactive.py:277
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1" msgid "Page down: Enter|N/n"
msgstr "提示: 输入 g+节点ID 显示节点下主机. 如: g1" msgstr "下一页: Enter|N/n"
#: coco/interactive.py:186 #: coco/interactive.py:278
msgid "There is no matched node, please re-enter" msgid "BACK: b/q"
msgstr "没有匹配分组,请重新输入" msgstr "返回: B/b"
#: coco/interactive.py:197 #: coco/interactive.py:299
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: coco/interactive.py:197 #: coco/interactive.py:299
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: coco/interactive.py:197 #: coco/interactive.py:299
msgid "IP" msgid "IP"
msgstr "" msgstr ""
#: coco/interactive.py:197 #: coco/interactive.py:299
msgid "LoginAs" msgid "LoginAs"
msgstr "登录用户" msgstr "登录用户"
#: coco/interactive.py:211 #: coco/interactive.py:313
msgid "Comment" msgid "Comment"
msgstr "备注" msgstr "备注"
#: coco/interactive.py:221 #: coco/interactive.py:322
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}" msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr "页码: {}, 数量: {}, 总页数: {}, 总数量: {}" msgstr "页码: {}, 数量: {}, 总页数: {}, 总数量: {}"
#: coco/interactive.py:296 #: coco/interactive.py:394
msgid "Select a login:: " msgid "No Nodes"
msgstr "选择一个登录:" msgstr "没有节点"
#: coco/interactive.py:319
msgid ""
"Terminal does not support login Windows, please use web terminal to access"
msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:401 #: coco/interactive.py:398
msgid "Tips: Enter the asset ID and log directly into the asset." msgid "Node: [ ID.Name(Asset amount) ]"
msgstr "提示: 输入资产ID,直接登录资产." msgstr "节点: [ ID.名称(资产数量) ]"
#: coco/interactive.py:402 #: coco/interactive.py:400
msgid "Page up: P/p" msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr "上一页: P/p" msgstr "提示: 输入 g+节点ID 显示节点下主机. 如: g1"
#: coco/interactive.py:403 #: coco/interactive.py:408
msgid "Page down: Enter|N/n" msgid "There is no matched node, please re-enter"
msgstr "下一页: Enter|N/n" msgstr "没有匹配分组,请重新输入"
#: coco/interactive.py:404 #: coco/interactive.py:438
msgid "BACK: B/b" msgid "Select a login:: "
msgstr "返回: B/b" msgstr "选择一个登录:"
#: coco/interactive.py:425 #: coco/interactive.py:461
msgid "No system user" msgid "No system user"
msgstr "没有系统用户" msgstr "没有系统用户"
#: coco/models.py:247 #: coco/models.py:247
msgid "Command `{}` is forbidden ........" msgid "Command `{}` is forbidden ........"
msgstr "" msgstr "命令 `{}` 是被禁止的 ..."
#: coco/proxy.py:89 #: coco/proxy.py:89
msgid "No permission" msgid "No permission"
...@@ -182,5 +175,14 @@ msgstr "开始连接到 {}@{} {:.1f}" ...@@ -182,5 +175,14 @@ msgstr "开始连接到 {}@{} {:.1f}"
msgid "Terminated by administrator" msgid "Terminated by administrator"
msgstr "被管理员中断" msgstr "被管理员中断"
#~ msgid "No"
#~ msgstr "无"
#~ msgid "Name"
#~ msgstr "名称"
#~ msgid "Total: {}"
#~ msgstr "总共: {}"
#~ msgid "Total: {} Match: {}" #~ msgid "Total: {} Match: {}"
#~ msgstr "总共: {} 匹配: {}" #~ msgstr "总共: {} 匹配: {}"
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