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
6 years ago
by
老广
Committed by
GitHub
6 years ago
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
()
...
...
This diff is collapsed.
Click to expand it.
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
)
...
...
This diff is collapsed.
Click to expand it.
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")
This diff is collapsed.
Click to expand it.
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'
],
...
...
This diff is collapsed.
Click to expand it.
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
):
"""
...
...
This diff is collapsed.
Click to expand it.
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
:
...
...
This diff is collapsed.
Click to expand it.
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
:
...
...
This diff is collapsed.
Click to expand it.
locale/en/LC_MESSAGES/coco.mo
View file @
2983f06a
No preview for this file type
This diff is collapsed.
Click to expand it.
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 ""
...
...
This diff is collapsed.
Click to expand it.
locale/zh_CN/LC_MESSAGES/coco.mo
View file @
2983f06a
No preview for this file type
This diff is collapsed.
Click to expand it.
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 "总共: {} 匹配: {}"
This diff is collapsed.
Click to expand it.
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