Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
C
coco
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
coco
Commits
2983f06a
Unverified
Commit
2983f06a
authored
Dec 19, 2018
by
老广
Committed by
GitHub
Dec 19, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #145 from jumpserver/dev
Dev
parents
1233b998
f510d762
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
420 additions
and
365 deletions
+420
-365
app.py
coco/app.py
+10
-0
config.py
coco/config.py
+18
-1
interactive.py
coco/interactive.py
+273
-248
logger.py
coco/logger.py
+1
-1
models.py
coco/models.py
+5
-0
sshd.py
coco/sshd.py
+2
-1
utils.py
coco/utils.py
+5
-1
coco.mo
locale/en/LC_MESSAGES/coco.mo
+0
-0
coco.po
locale/en/LC_MESSAGES/coco.po
+41
-50
coco.mo
locale/zh_CN/LC_MESSAGES/coco.mo
+0
-0
coco.po
locale/zh_CN/LC_MESSAGES/coco.po
+65
-63
No files found.
coco/app.py
View file @
2983f06a
...
...
@@ -56,6 +56,7 @@ class Coco:
return
self
.
_task_handler
@staticmethod
@ignore_error
def
load_extra_conf_from_server
():
configs
=
app_service
.
load_config_from_server
()
logger
.
debug
(
"Loading config from server: {}"
.
format
(
...
...
@@ -63,8 +64,17 @@ class Coco:
))
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
):
self
.
load_extra_conf_from_server
()
self
.
keep_load_extra_conf
()
self
.
keep_heartbeat
()
self
.
monitor_sessions
()
self
.
monitor_sessions_replay
()
...
...
coco/config.py
View file @
2983f06a
...
...
@@ -92,8 +92,9 @@ class Config(dict):
"""
def
__init__
(
self
,
root_path
,
defaults
=
None
):
dict
.
__init__
(
self
,
defaults
or
{})
self
.
defaults
=
defaults
or
{}
self
.
root_path
=
root_path
super
()
.
__init__
({})
def
from_envvar
(
self
,
variable_name
,
silent
=
False
):
"""Loads a configuration from an environment variable pointing to
...
...
@@ -269,6 +270,21 @@ class Config(dict):
rv
[
key
]
=
v
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
):
return
'<
%
s
%
s>'
%
(
self
.
__class__
.
__name__
,
dict
.
__repr__
(
self
))
...
...
@@ -302,6 +318,7 @@ default_config = {
'REPLAY_STORAGE'
:
{
'TYPE'
:
'server'
},
'LANGUAGE_CODE'
:
'zh'
,
'SECURITY_MAX_IDLE_TIME'
:
60
,
'ASSET_LIST_PAGE_SIZE'
:
'auto'
,
}
config
=
Config
(
root_path
,
default_config
)
...
...
coco/interactive.py
View file @
2983f06a
...
...
@@ -32,47 +32,51 @@ class InteractiveServer:
def
__init__
(
self
,
client
):
self
.
client
=
client
self
.
closed
=
False
self
.
_
search_result
=
None
self
.
_
results
=
None
self
.
nodes
=
None
self
.
offset
=
0
self
.
limit
=
100
self
.
assets
_list
=
[]
self
.
assets
=
[]
self
.
finish
=
False
self
.
page
=
1
self
.
total_assets
=
0
self
.
total_count
=
0
# 分页展示中
用来存放数目总条数
self
.
node
s
_tree
=
None
# 授权节点树
self
.
get_user_assets_
paging_
async
()
self
.
total_assets
=
0
# 用户被授权的所有资产
self
.
total_count
=
0
# 分页展示中
的资产总数量
self
.
node_tree
=
None
# 授权节点树
self
.
get_user_assets_async
()
self
.
get_user_nodes_async
()
@property
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
def
search_result
(
self
):
if
self
.
_
search_result
:
return
self
.
_
search_result
def
results
(
self
):
if
self
.
_
results
:
return
self
.
_
results
else
:
return
[]
@search_result.setter
def
search_result
(
self
,
value
):
if
not
value
:
self
.
_search_result
=
value
return
value
=
self
.
filter_system_users
(
value
)
self
.
_search_result
=
value
@results.setter
def
results
(
self
,
value
):
self
.
_results
=
value
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
'
))
#
# Display banner
#
def
display_banner
(
self
):
self
.
client
.
send
(
char
.
CLEAR_CHAR
)
...
...
@@ -99,15 +103,25 @@ class InteractiveServer:
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
):
if
opt
is
None
:
return
self
.
_sentinel
elif
opt
.
startswith
(
"/"
):
self
.
search_and_display
(
opt
.
lstrip
(
"/"
))
self
.
search_and_display
_assets
(
opt
.
lstrip
(
"/"
))
elif
opt
in
[
'p'
,
'P'
,
''
]:
self
.
display_assets
()
elif
opt
in
[
'g'
,
'G'
]:
self
.
display_nodes_tree
()
self
.
display_nodes_
as_
tree
()
elif
opt
.
startswith
(
"g"
)
and
opt
.
lstrip
(
"g"
)
.
isdigit
():
self
.
display_node_assets
(
int
(
opt
.
lstrip
(
"g"
)))
elif
opt
in
[
'q'
,
'Q'
,
'exit'
,
'quit'
]:
...
...
@@ -121,98 +135,172 @@ class InteractiveServer:
elif
opt
in
[
'h'
,
'H'
]:
self
.
display_banner
()
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
):
self
.
get_user_assets_
paging_
async
()
self
.
get_user_assets_async
()
self
.
get_user_nodes_async
()
def
search_assets
(
self
,
q
):
if
not
self
.
finish
:
assets
=
app_service
.
get_search_user_granted_assets
(
self
.
client
.
user
,
q
)
return
assets
assets
=
self
.
assets_list
if
self
.
finish
:
assets
=
self
.
search_assets_from_local
(
q
)
else
:
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
=
[]
# 所有的
if
q
in
(
''
,
None
):
result
=
assets
result
=
self
.
assets
# 全匹配到则直接返回全匹配的
if
len
(
result
)
==
0
:
_result
=
[
asset
for
asset
in
assets
if
is_obj_attr_eq
(
asset
,
q
)]
_result
=
[
asset
for
asset
in
self
.
assets
if
is_obj_attr_eq
(
asset
,
q
)]
if
len
(
_result
)
==
1
:
result
=
_result
# 最后模糊匹配
if
len
(
result
)
==
0
:
result
=
[
asset
for
asset
in
assets
if
is_obj_attr_has
(
asset
,
q
)]
result
=
[
asset
for
asset
in
self
.
assets
if
is_obj_attr_has
(
asset
,
q
)]
return
result
def
display_assets
(
self
):
"""
Display user all assets
:return:
"""
self
.
display_result_paging
(
self
.
assets_list
)
#
# Display assets
#
def
display_nodes
(
self
):
if
self
.
nodes
is
None
:
self
.
get_user_nodes
()
def
display_assets
(
self
,
assets
=
None
):
if
assets
is
None
:
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
.
node
s
)
==
0
:
self
.
client
.
send
(
w
arning
(
_
(
"No"
)
))
if
len
(
asset
s
)
==
0
:
self
.
client
.
send
(
w
r
(
_
(
"No Assets"
),
before
=
0
))
return
id_length
=
max
(
len
(
str
(
len
(
self
.
nodes
))),
5
)
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
.
total_count
=
self
.
total_assets
if
assets
is
self
.
assets
else
len
(
assets
)
self
.
client
.
send
(
wr
(
title
(
format_with_zh
(
size_list
,
*
fake_data
))))
for
index
,
node
in
enumerate
(
self
.
nodes
,
1
):
data
=
[
index
,
node
.
name
,
node
.
assets_amount
]
self
.
client
.
send
(
wr
(
format_with_zh
(
size_list
,
*
data
)))
self
.
client
.
send
(
wr
(
_
(
"Total: {}"
)
.
format
(
len
(
self
.
nodes
)),
before
=
1
))
action
=
None
gen
=
self
.
_page_generator
(
assets
)
while
True
:
try
:
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
):
if
self
.
nodes
is
None
:
self
.
get_user_nodes
()
def
_page_generator
(
self
,
assets
):
start
,
page
=
0
,
1
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
))
return
# 退出
if
action
==
BACK
:
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
)
self
.
client
.
send
(
wr
(
title
(
_
(
"Node: [ ID.Name(Asset amount) ]"
)),
before
=
0
)
)
self
.
client
.
send
(
wr
(
self
.
nodes_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_a_page_assets
(
self
,
page
,
assets
):
self
.
client
.
send
(
char
.
CLEAR_CHAR
)
self
.
page
=
page
self
.
results
=
assets
self
.
display_results
(
)
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_tree
(
)
return
def
display_
page_bottom_prompt
(
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
))
assets
=
self
.
nodes
[
_id
-
1
]
.
assets_granted
self
.
display_result_paging
(
assets
)
def
get_user_action
(
self
):
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"
]
self
.
search_result
=
sort_assets
(
self
.
search_result
,
sort_by
)
self
.
results
=
sort_assets
(
self
.
results
,
sort_by
)
fake_data
=
[
_
(
"ID"
),
_
(
"Hostname"
),
_
(
"IP"
),
_
(
"LoginAs"
)]
id_length
=
max
(
len
(
str
(
len
(
self
.
search_result
))),
4
)
hostname_length
=
item_max_length
(
self
.
search_result
,
15
,
id_length
=
max
(
len
(
str
(
len
(
self
.
results
))),
4
)
hostname_length
=
item_max_length
(
self
.
results
,
15
,
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
)
size_list
=
[
id_length
,
hostname_length
,
16
,
sysuser_length
]
header_without_comment
=
format_with_zh
(
size_list
,
*
fake_data
)
...
...
@@ -224,43 +312,110 @@ class InteractiveServer:
size_list
.
append
(
comment_length
)
fake_data
.
append
(
_
(
"Comment"
))
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
=
[
index
,
asset
.
hostname
,
asset
.
ip
,
asset
.
system_users_name_list
,
asset
.
comment
]
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
.
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
)
self
.
display_result_paging
(
assets
)
#
# Get assets
#
def
get_user_nodes
(
self
):
self
.
nodes
=
app_service
.
get_user_asset_groups
(
self
.
client
.
user
)
self
.
sort_nodes
()
self
.
construct_nodes_tree
()
def
get_user_assets_async
(
self
):
if
self
.
need_paging
:
thread
=
threading
.
Thread
(
target
=
self
.
get_user_assets_paging
)
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
):
self
.
nodes
=
sorted
(
self
.
nodes
,
key
=
lambda
node
:
node
.
key
)
logger
.
info
(
'Get user assets paging async: {}'
.
format
(
len
(
assets
)))
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
):
self
.
nodes_tree
=
Tree
()
def
get_user_nodes
(
self
):
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'
self
.
node
s
_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
):
tag
=
"{}.{}({})"
.
format
(
index
+
1
,
node
.
name
,
node
.
assets_amount
)
key
=
node
.
key
parent_key
=
key
[:
node
.
key
.
rfind
(
':'
)]
or
root
self
.
node
s
_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
):
thread
=
threading
.
Thread
(
target
=
self
.
get_user_nodes
)
thread
.
start
()
def
display_nodes_as_tree
(
self
):
if
self
.
nodes
is
None
:
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
def
filter_system_users
(
assets
):
...
...
@@ -273,26 +428,6 @@ class InteractiveServer:
asset
.
system_users_granted
=
system_users_cleaned
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
):
if
len
(
system_users
)
==
1
:
return
system_users
[
0
]
...
...
@@ -316,124 +451,9 @@ class InteractiveServer:
for
index
,
system_user
in
enumerate
(
system_users
):
self
.
client
.
send
(
wr
(
"{} {}"
.
format
(
index
,
system_user
.
name
)))
def
search_and_proxy
(
self
,
opt
):
assets
=
self
.
search_assets
(
opt
)
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
#
# Proxy
#
def
proxy
(
self
,
asset
):
system_user
=
self
.
choose_system_user
(
asset
.
system_users_granted
)
...
...
@@ -443,6 +463,10 @@ class InteractiveServer:
forwarder
=
ProxyServer
(
self
.
client
,
asset
,
system_user
)
forwarder
.
proxy
()
#
# Entrance
#
def
interact
(
self
):
self
.
display_banner
()
while
not
self
.
closed
:
...
...
@@ -456,15 +480,16 @@ class InteractiveServer:
break
self
.
close
()
def
interact_async
(
self
):
thread
=
threading
.
Thread
(
target
=
self
.
interact
)
thread
.
daemon
=
True
thread
.
start
()
def
close
(
self
):
logger
.
debug
(
"Interactive server server close: {}"
.
format
(
self
))
self
.
closed
=
True
# current_app.remove_client(self.client)
def
interact_async
(
self
):
# 目前没用
thread
=
threading
.
Thread
(
target
=
self
.
interact
)
thread
.
daemon
=
True
thread
.
start
()
# def __del__(self):
# print("GC: Interactive class been gc")
coco/logger.py
View file @
2983f06a
...
...
@@ -10,7 +10,7 @@ from .config import config as app_config
def
create_logger
():
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'
)
main_setting
=
{
'handlers'
:
[
'console'
,
'file'
],
...
...
coco/models.py
View file @
2983f06a
...
...
@@ -360,6 +360,11 @@ class TelnetServer(BaseServer):
self
.
system_user
=
system_user
super
(
TelnetServer
,
self
)
.
__init__
(
chan
=
sock
)
@property
def
closed
(
self
):
""" self.chan: socket object """
return
getattr
(
self
.
chan
,
'_closed'
,
False
)
class
Server
(
BaseServer
):
"""
...
...
coco/sshd.py
View file @
2983f06a
...
...
@@ -63,6 +63,7 @@ class SSHServer:
return
connection
def
handle_connection
(
self
,
sock
,
addr
):
logger
.
debug
(
"Handle new connection from: {}"
.
format
(
addr
))
transport
=
paramiko
.
Transport
(
sock
,
gss_kex
=
False
)
try
:
transport
.
load_server_moduli
()
...
...
@@ -77,7 +78,7 @@ class SSHServer:
server
=
SSHInterface
(
connection
)
try
:
transport
.
start_server
(
server
=
server
)
except
paramiko
.
SSHException
:
except
(
paramiko
.
SSHException
,
socket
.
timeout
)
:
logger
.
warning
(
"SSH negotiation failed"
)
return
except
EOFError
as
e
:
...
...
coco/utils.py
View file @
2983f06a
...
...
@@ -294,7 +294,7 @@ def get_logger(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, 提示用户输入, 获取并返回
:return user input string
...
...
@@ -303,6 +303,10 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0):
parser
=
TtyIOParser
()
client
.
send
(
wrap_with_line_feed
(
prompt
,
before
=
before
,
after
=
after
))
if
only_one_char
:
data
=
client
.
recv
(
1
)
return
data
.
decode
()
while
True
:
data
=
client
.
recv
(
10
)
if
len
(
data
)
==
0
:
...
...
locale/en/LC_MESSAGES/coco.mo
View file @
2983f06a
No preview for this file type
locale/en/LC_MESSAGES/coco.po
View file @
2983f06a
...
...
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-1
0-31 11:49
+0800\n"
"POT-Creation-Date: 2018-1
2-18 20:03
+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/en/LC\n"
...
...
@@ -16,11 +16,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:1
3
5
#: coco/app.py:1
4
5
msgid "Connect idle more than {} minutes, disconnect"
msgstr ""
#: coco/interactive.py:8
0
#: coco/interactive.py:8
4
#, python-brace-format
msgid ""
"\n"
...
...
@@ -28,136 +28,127 @@ msgid ""
"{end}{R}{R}"
msgstr ""
#: coco/interactive.py:8
2
#: coco/interactive.py:8
6
#, python-brace-format
msgid ""
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, "
"Comment{end} to search login(if unique).{R}"
msgstr ""
#: coco/interactive.py:8
3
#: coco/interactive.py:8
7
#, python-brace-format
msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
"search, such as: /ip.{R}"
msgstr ""
#: coco/interactive.py:8
4
#: coco/interactive.py:8
8
#, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr ""
#: coco/interactive.py:8
5
#: coco/interactive.py:8
9
#, python-brace-format
msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr ""
#: coco/interactive.py:
86
#: coco/interactive.py:
90
#, python-brace-format
msgid ""
"{T}5) Enter {green}g{end} + {green}NodeID{end} to display the host under the "
"node, such as g1.{R}"
msgstr ""
#: coco/interactive.py:
87
#: coco/interactive.py:
91
#, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr ""
#: coco/interactive.py:
88
#: coco/interactive.py:
92
#, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr ""
#: coco/interactive.py:
89
#: coco/interactive.py:
93
#, python-brace-format
msgid "{T}8) Enter {green}r{end} to refresh your assets and nodes.{R}"
msgstr ""
#: coco/interactive.py:9
0
#: coco/interactive.py:9
4
#, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr ""
#: coco/interactive.py:159
msgid "No"
msgstr ""
#: coco/interactive.py:166
msgid "Name"
#: coco/interactive.py:155
msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr ""
#: coco/interactive.py:
166
msgid "Assets"
#: coco/interactive.py:
212
msgid "
No
Assets"
msgstr ""
#: coco/interactive.py:
172
msgid "T
otal: {}
"
#: coco/interactive.py:
275
msgid "T
ips: Enter the asset ID and log directly into the asset.
"
msgstr ""
#: coco/interactive.py:
177
msgid "
Node [ ID.Name(Asset) ]
"
#: coco/interactive.py:
276
msgid "
Page up: P/p
"
msgstr ""
#: coco/interactive.py:
179
msgid "
Enter g+NodeID to display the host under the node, such as g1.
"
#: coco/interactive.py:
277
msgid "
Page down: Enter|N/n
"
msgstr ""
#: coco/interactive.py:
186
msgid "
There is no matched node, please re-enter
"
#: coco/interactive.py:
278
msgid "
BACK: b/q
"
msgstr ""
#: coco/interactive.py:
197
#: coco/interactive.py:
299
msgid "ID"
msgstr ""
#: coco/interactive.py:
197
#: coco/interactive.py:
299
msgid "Hostname"
msgstr ""
#: coco/interactive.py:
197
#: coco/interactive.py:
299
msgid "IP"
msgstr ""
#: coco/interactive.py:
197
#: coco/interactive.py:
299
msgid "LoginAs"
msgstr ""
#: coco/interactive.py:
211
#: coco/interactive.py:
313
msgid "Comment"
msgstr ""
#: coco/interactive.py:
221
#: coco/interactive.py:
322
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr ""
#: coco/interactive.py:
296
msgid "
Select a login::
"
#: coco/interactive.py:
394
msgid "
No Nodes
"
msgstr ""
#: coco/interactive.py:319
msgid ""
"Terminal does not support login Windows, please use web terminal to access"
#: coco/interactive.py:398
msgid "Node: [ ID.Name(Asset amount) ]"
msgstr ""
#: coco/interactive.py:40
1
msgid "Tips: Enter
the asset ID and log directly into the asset.
"
#: coco/interactive.py:40
0
msgid "Tips: Enter
g+NodeID to display the host under the node, such as g1
"
msgstr ""
#: coco/interactive.py:402
msgid "Page up: P/p"
msgstr ""
#: coco/interactive.py:403
msgid "Page down: Enter|N/n"
#: coco/interactive.py:408
msgid "There is no matched node, please re-enter"
msgstr ""
#: coco/interactive.py:4
04
msgid "
BACK: B/b
"
#: coco/interactive.py:4
38
msgid "
Select a login::
"
msgstr ""
#: coco/interactive.py:4
25
#: coco/interactive.py:4
61
msgid "No system user"
msgstr ""
...
...
locale/zh_CN/LC_MESSAGES/coco.mo
View file @
2983f06a
No preview for this file type
locale/zh_CN/LC_MESSAGES/coco.po
View file @
2983f06a
...
...
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-1
0-31 11:49
+0800\n"
"POT-Creation-Date: 2018-1
2-18 20:04
+0800\n"
"PO-Revision-Date: 2018-08-10 10:42+0800\n"
"Last-Translator: BaiJiangjie <bugatti_it@163.com>\n"
"Language-Team: Language locale/zh\n"
...
...
@@ -16,11 +16,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:1
3
5
#: coco/app.py:1
4
5
msgid "Connect idle more than {} minutes, disconnect"
msgstr "空闲时间超过 {} 分钟,断开连接"
#: coco/interactive.py:8
0
#: coco/interactive.py:8
4
#, python-brace-format
msgid ""
"\n"
...
...
@@ -30,7 +30,7 @@ msgstr ""
"\n"
"{T}{T}{title} {user}, 欢迎使用Jumpserver开源跳板机系统 {end}{R}{R}"
#: coco/interactive.py:8
2
#: coco/interactive.py:8
6
#, python-brace-format
msgid ""
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, "
...
...
@@ -39,7 +39,7 @@ msgstr ""
"{T}1) 输入 {green}ID{end} 直接登录 或 输入{green}部分 IP,主机名,备注{end} 进"
"行搜索登录(如果唯一).{R}"
#: coco/interactive.py:8
3
#: coco/interactive.py:8
7
#, python-brace-format
msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
...
...
@@ -48,127 +48,120 @@ msgstr ""
"{T}2) 输入 {green}/{end} + {green}IP, 主机名{end} or {green}备注 {end}搜索. "
"如: /ip{R}"
#: coco/interactive.py:8
4
#: coco/interactive.py:8
8
#, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr "{T}3) 输入 {green}p{end} 显示您有权限的主机.{R}"
#: coco/interactive.py:8
5
#: coco/interactive.py:8
9
#, python-brace-format
msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr "{T}4) 输入 {green}g{end} 显示您有权限的节点.{R}"
#: coco/interactive.py:86
#: coco/interactive.py:90
#, python-brace-format
msgid ""
"{T}5) Enter {green}g{end} + {green}NodeID{end} to display the host under the "
"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
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr "{T}6) 输入 {green}s{end} 中/英文切换.{R}"
#: coco/interactive.py:
88
#: coco/interactive.py:
92
#, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr "{T}7) 输入 {green}h{end} 帮助.{R}"
#: coco/interactive.py:
89
#: coco/interactive.py:
93
#, python-brace-format
msgid "{T}8) Enter {green}r{end} to refresh your assets and nodes.{R}"
msgstr "{T}0) 输入 {green}r{end} 刷新最新的机器和节点信息.{R}"
#: coco/interactive.py:9
0
#: coco/interactive.py:9
4
#, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr "{T}0) 输入 {green}q{end} 退出.{R}"
#: coco/interactive.py:159
msgid "No"
msgstr "无"
#: coco/interactive.py:166
msgid "Name"
msgstr "名称"
#: coco/interactive.py:155
msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:
166
msgid "Assets"
msgstr "资产"
#: coco/interactive.py:
212
msgid "
No
Assets"
msgstr "
没有
资产"
#: coco/interactive.py:
172
msgid "T
otal: {}
"
msgstr "
总共: {}
"
#: coco/interactive.py:
275
msgid "T
ips: Enter the asset ID and log directly into the asset.
"
msgstr "
提示: 输入资产ID,直接登录资产.
"
#: coco/interactive.py:
177
msgid "
Node: [ ID.Name(Asset amount) ]
"
msgstr "
节点: [ ID.名称(资产数量) ]
"
#: coco/interactive.py:
276
msgid "
Page up: P/p
"
msgstr "
上一页: P/p
"
#: coco/interactive.py:
179
msgid "
Tips: Enter g+NodeID to display the host under the node, such as g1
"
msgstr "
提示: 输入 g+节点ID 显示节点下主机. 如: g1
"
#: coco/interactive.py:
277
msgid "
Page down: Enter|N/n
"
msgstr "
下一页: Enter|N/n
"
#: coco/interactive.py:
186
msgid "
There is no matched node, please re-enter
"
msgstr "
没有匹配分组,请重新输入
"
#: coco/interactive.py:
278
msgid "
BACK: b/q
"
msgstr "
返回: B/b
"
#: coco/interactive.py:
197
#: coco/interactive.py:
299
msgid "ID"
msgstr ""
#: coco/interactive.py:
197
#: coco/interactive.py:
299
msgid "Hostname"
msgstr "主机名"
#: coco/interactive.py:
197
#: coco/interactive.py:
299
msgid "IP"
msgstr ""
#: coco/interactive.py:
197
#: coco/interactive.py:
299
msgid "LoginAs"
msgstr "登录用户"
#: coco/interactive.py:
211
#: coco/interactive.py:
313
msgid "Comment"
msgstr "备注"
#: coco/interactive.py:
221
#: coco/interactive.py:
322
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr "页码: {}, 数量: {}, 总页数: {}, 总数量: {}"
#: coco/interactive.py:296
msgid "Select a login:: "
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:394
msgid "No Nodes"
msgstr "没有节点"
#: coco/interactive.py:
401
msgid "
Tips: Enter the asset ID and log directly into the asset.
"
msgstr "
提示: 输入资产ID,直接登录资产.
"
#: coco/interactive.py:
398
msgid "
Node: [ ID.Name(Asset amount) ]
"
msgstr "
节点: [ ID.名称(资产数量) ]
"
#: coco/interactive.py:40
2
msgid "
Page up: P/p
"
msgstr "
上一页: P/p
"
#: coco/interactive.py:40
0
msgid "
Tips: Enter g+NodeID to display the host under the node, such as g1
"
msgstr "
提示: 输入 g+节点ID 显示节点下主机. 如: g1
"
#: coco/interactive.py:40
3
msgid "
Page down: Enter|N/n
"
msgstr "
下一页: Enter|N/n
"
#: coco/interactive.py:40
8
msgid "
There is no matched node, please re-enter
"
msgstr "
没有匹配分组,请重新输入
"
#: coco/interactive.py:4
04
msgid "
BACK: B/b
"
msgstr "
返回: B/b
"
#: coco/interactive.py:4
38
msgid "
Select a login::
"
msgstr "
选择一个登录:
"
#: coco/interactive.py:4
25
#: coco/interactive.py:4
61
msgid "No system user"
msgstr "没有系统用户"
#: coco/models.py:247
msgid "Command `{}` is forbidden ........"
msgstr ""
msgstr "
命令 `{}` 是被禁止的 ...
"
#: coco/proxy.py:89
msgid "No permission"
...
...
@@ -182,5 +175,14 @@ msgstr "开始连接到 {}@{} {:.1f}"
msgid "Terminated by administrator"
msgstr "被管理员中断"
#~ msgid "No"
#~ msgstr "无"
#~ msgid "Name"
#~ msgstr "名称"
#~ msgid "Total: {}"
#~ msgstr "总共: {}"
#~ msgid "Total: {} Match: {}"
#~ msgstr "总共: {} 匹配: {}"
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment