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
c3af966b
Commit
c3af966b
authored
Mar 09, 2019
by
ibuler
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'dev' of
https://github.com/jumpserver/coco
into dev
parents
73757b36
97865db8
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
363 additions
and
213 deletions
+363
-213
.gitignore
.gitignore
+1
-2
Dockerfile
Dockerfile
+2
-2
app.py
coco/app.py
+54
-39
conf.py
coco/conf.py
+11
-1
connection.py
coco/connection.py
+26
-16
interactive.py
coco/interactive.py
+13
-12
logger.py
coco/logger.py
+3
-4
models.py
coco/models.py
+3
-0
proxy.py
coco/proxy.py
+6
-0
recorder.py
coco/recorder.py
+57
-33
session.py
coco/session.py
+8
-1
sftp.py
coco/sftp.py
+6
-1
utils.py
coco/utils.py
+8
-0
cocod
cocod
+19
-1
config_example.yml
config_example.yml
+9
-3
entrypoint.sh
entrypoint.sh
+17
-0
coco.mo
locale/en/LC_MESSAGES/coco.mo
+0
-0
coco.po
locale/en/LC_MESSAGES/coco.po
+58
-47
coco.mo
locale/zh_CN/LC_MESSAGES/coco.mo
+0
-0
coco.po
locale/zh_CN/LC_MESSAGES/coco.po
+61
-50
requirements.txt
requirements/requirements.txt
+1
-1
No files found.
.gitignore
View file @
c3af966b
...
...
@@ -4,8 +4,7 @@ env/
*.pyo
.access_key
*.log
logs
/*
data
/*
host_rsa_key
sessions/*
coco.pid
config.yml
Dockerfile
View file @
c3af966b
...
...
@@ -6,7 +6,7 @@ WORKDIR /opt/coco
RUN
yum
-y
install
epel-release
RUN
cd
requirements
&&
yum
-y
install
$(
cat
rpm_requirements.txt
)
RUN
cd
requirements
&&
pip
install
$(
egrep
"jumpserver|jms"
requirements.txt |
tr
'\n'
' '
)
&&
pip
install
-r
requirements.txt
-i
https://mirrors.ustc.edu.cn/pypi/web/simple
RUN
cd
requirements
&&
pip
install
-r
requirements.txt
-i
https://mirrors.aliyun.com/pypi/simple/
||
pip
install
-r
requirements.txt
ENV
LANG=zh_CN.UTF-8
ENV
LC_ALL=zh_CN.UTF-8
...
...
@@ -17,4 +17,4 @@ VOLUME /opt/coco/data
RUN
echo
>
config.yml
EXPOSE
2222
CMD
python run_server.py
ENTRYPOINT
["./entrypoint.sh"]
coco/app.py
View file @
c3af966b
...
...
@@ -10,8 +10,6 @@ import json
import
signal
import
copy
import
psutil
from
.conf
import
config
from
.sshd
import
SSHServer
from
.httpd
import
HttpServer
...
...
@@ -23,7 +21,7 @@ from .session import Session
from
.models
import
Connection
__version__
=
'1.4.
6
'
__version__
=
'1.4.
8
'
BASE_DIR
=
os
.
path
.
dirname
(
os
.
path
.
dirname
(
__file__
))
logger
=
get_logger
(
__file__
)
...
...
@@ -39,6 +37,7 @@ class Coco:
self
.
replay_recorder_class
=
None
self
.
command_recorder_class
=
None
self
.
_task_handler
=
None
self
.
first_load_extra_conf
=
True
@property
def
sshd
(
self
):
...
...
@@ -58,17 +57,18 @@ class Coco:
self
.
_task_handler
=
TaskHandler
()
return
self
.
_task_handler
@staticmethod
@ignore_error
def
load_extra_conf_from_server
():
def
load_extra_conf_from_server
(
self
):
configs
=
app_service
.
load_config_from_server
()
config
.
update
(
configs
)
tmp
=
copy
.
deepcopy
(
configs
)
tmp
[
'HOST_KEY'
]
=
tmp
[
'HOST_KEY'
][
32
:
50
]
+
'...'
logger
.
debug
(
"Loading config from server: {}"
.
format
(
json
.
dumps
(
tmp
)
))
tmp
[
'HOST_KEY'
]
=
tmp
.
get
(
'HOST_KEY'
,
''
)[
32
:
50
]
+
'...'
if
self
.
first_load_extra_conf
:
logger
.
debug
(
"Loading config from server: {}"
.
format
(
json
.
dumps
(
tmp
)
))
self
.
first_load_extra_conf
=
False
def
keep_load_extra_conf
(
self
):
def
func
():
...
...
@@ -83,24 +83,25 @@ class Coco:
self
.
keep_load_extra_conf
()
self
.
keep_heartbeat
()
self
.
monitor_sessions
()
self
.
monitor_sessions_replay
()
if
config
.
UPLOAD_FAILED_REPLAY_ON_START
:
self
.
upload_failed_replay
()
# @ignore_error
def
heartbeat
(
self
):
sessions
=
list
(
Session
.
sessions
.
keys
())
p
=
psutil
.
Process
(
os
.
getpid
())
cpu_used
=
p
.
cpu_percent
(
interval
=
1.0
)
memory_used
=
int
(
p
.
memory_info
()
.
rss
/
1024
/
1024
)
connections
=
len
(
p
.
connections
())
threads
=
p
.
num_threads
()
session_online
=
len
(
sessions
)
#
p = psutil.Process(os.getpid())
#
cpu_used = p.cpu_percent(interval=1.0)
#
memory_used = int(p.memory_info().rss / 1024 / 1024)
#
connections = len(p.connections())
#
threads = p.num_threads()
#
session_online = len(sessions)
data
=
{
"cpu_used"
:
cpu_used
,
"memory_used"
:
memory_used
,
"connections"
:
connections
,
"threads"
:
threads
,
"boot_time"
:
p
.
create_time
(),
"session_online"
:
session_online
,
#
"cpu_used": cpu_used,
#
"memory_used": memory_used,
#
"connections": connections,
#
"threads": threads,
#
"boot_time": p.create_time(),
#
"session_online": session_online,
"sessions"
:
sessions
,
}
tasks
=
app_service
.
terminal_heartbeat
(
data
)
...
...
@@ -131,28 +132,42 @@ class Coco:
thread
=
threading
.
Thread
(
target
=
func
)
thread
.
start
()
def
monitor_sessions_replay
(
self
):
interval
=
10
log_dir
=
os
.
path
.
join
(
config
[
'LOG_DIR'
])
@staticmethod
def
upload_failed_replay
():
replay_dir
=
os
.
path
.
join
(
config
.
REPLAY_DIR
)
def
retry_upload_replay
(
session_id
,
file_gz_path
,
target
):
recorder
=
get_replay_recorder
()
recorder
.
file_gz_path
=
file_gz_path
recorder
.
session_id
=
session_id
recorder
.
target
=
target
recorder
.
upload_replay
()
def
check_replay_is_need_upload
(
full_path
):
filename
=
os
.
path
.
basename
(
full_path
)
suffix
=
filename
.
split
(
'.'
)[
-
1
]
if
suffix
!=
'gz'
:
return
False
session_id
=
filename
.
split
(
'.'
)[
0
]
if
len
(
session_id
)
!=
36
:
return
False
return
True
def
func
():
while
not
self
.
stop_evt
.
is_set
():
active_sessions
=
[
sid
for
sid
in
Session
.
sessions
]
for
filename
in
os
.
listdir
(
log_dir
):
if
not
os
.
path
.
isdir
(
replay_dir
):
return
for
d
in
os
.
listdir
(
replay_dir
):
date_path
=
os
.
path
.
join
(
replay_dir
,
d
)
for
filename
in
os
.
listdir
(
date_path
):
full_path
=
os
.
path
.
join
(
date_path
,
filename
)
session_id
=
filename
.
split
(
'.'
)[
0
]
full_path
=
os
.
path
.
join
(
log_dir
,
filename
)
if
len
(
session_id
)
!=
36
:
# 检查是否需要上传
if
not
check_replay_is_need_upload
(
full_path
):
continue
recorder
=
get_replay_recorder
()
if
session_id
not
in
active_sessions
:
recorder
.
file_path
=
full_path
ok
=
recorder
.
upload_replay
(
session_id
,
1
)
if
not
ok
and
os
.
path
.
getsize
(
full_path
)
==
0
:
os
.
unlink
(
full_path
)
logger
.
debug
(
"Retry upload retain replay: {}"
.
format
(
filename
))
target
=
os
.
path
.
join
(
d
,
filename
)
retry_upload_replay
(
session_id
,
full_path
,
target
)
time
.
sleep
(
1
)
time
.
sleep
(
interval
)
thread
=
threading
.
Thread
(
target
=
func
)
thread
.
start
()
...
...
coco/conf.py
View file @
c3af966b
...
...
@@ -296,6 +296,12 @@ class Config(dict):
return
value
value
=
os
.
environ
.
get
(
item
,
None
)
if
value
is
not
None
:
if
value
.
isdigit
():
value
=
int
(
value
)
elif
value
.
lower
()
==
'false'
:
value
=
False
elif
value
.
lower
()
==
'true'
:
value
=
True
return
value
return
self
.
defaults
.
get
(
item
)
...
...
@@ -330,7 +336,9 @@ defaults = {
'SECRET_KEY'
:
'SDK29K03
%
MM0ksf'
,
'LOG_LEVEL'
:
'INFO'
,
'LOG_DIR'
:
os
.
path
.
join
(
root_path
,
'data'
,
'logs'
),
'REPLAY_DIR'
:
os
.
path
.
join
(
root_path
,
'data'
,
'replays'
),
'ASSET_LIST_SORT_BY'
:
'hostname'
,
# hostname, ip
'TELNET_REGEX'
:
''
,
'PASSWORD_AUTH'
:
True
,
'PUBLIC_KEY_AUTH'
:
True
,
'SSH_TIMEOUT'
:
10
,
...
...
@@ -344,7 +352,9 @@ defaults = {
'LANGUAGE_CODE'
:
'zh'
,
'SECURITY_MAX_IDLE_TIME'
:
60
,
'ASSET_LIST_PAGE_SIZE'
:
'auto'
,
'SFTP_ROOT'
:
'tmp'
,
'SFTP_ROOT'
:
'/tmp'
,
'SFTP_SHOW_HIDDEN_FILE'
:
False
,
'UPLOAD_FAILED_REPLAY_ON_START'
:
True
}
...
...
coco/connection.py
View file @
c3af966b
# -*- coding: utf-8 -*-
#
import
os
import
re
import
socket
import
telnetlib
...
...
@@ -69,7 +68,7 @@ class SSHConnection:
look_for_keys
=
False
,
sock
=
sock
,
allow_agent
=
False
,
)
transport
=
ssh
.
get_transport
()
transport
.
set_keepalive
(
30
0
)
transport
.
set_keepalive
(
2
0
)
except
Exception
as
e
:
password_short
=
"None"
key_fingerprint
=
"None"
...
...
@@ -133,7 +132,9 @@ class SSHConnection:
except
:
continue
try
:
sock
=
ssh
.
get_transport
()
.
open_channel
(
transport
=
ssh
.
get_transport
()
transport
.
set_keep_alive
(
20
)
sock
=
transport
.
open_channel
(
'direct-tcpip'
,
(
asset
.
ip
,
asset
.
port
),
(
'127.0.0.1'
,
0
)
)
break
...
...
@@ -143,6 +144,17 @@ class SSHConnection:
class
TelnetConnection
:
incorrect_pattern
=
re
.
compile
(
r'incorrect|failed|失败|错误'
,
re
.
I
)
username_pattern
=
re
.
compile
(
r'login:?\s*$|username:?\s*$|用户名:?\s*$|账\s*号:?\s*$'
,
re
.
I
)
password_pattern
=
re
.
compile
(
r'Password:?\s*$|passwd:?\s*$|密\s*码:?\s*$'
,
re
.
I
)
success_pattern
=
re
.
compile
(
r'Last\s*login|success|成功|#|\$'
,
re
.
I
)
custom_success_pattern
=
None
def
__init__
(
self
,
asset
,
system_user
,
client
):
self
.
client
=
client
...
...
@@ -150,18 +162,13 @@ class TelnetConnection:
self
.
system_user
=
system_user
self
.
sock
=
None
self
.
sel
=
selectors
.
DefaultSelector
()
self
.
incorrect_pattern
=
re
.
compile
(
r'incorrect|failed|失败|错误'
,
re
.
I
)
self
.
username_pattern
=
re
.
compile
(
r'login:?\s*$|username:?\s*$|用户名:?\s*$|账\s*号:?\s*$'
,
re
.
I
)
self
.
password_pattern
=
re
.
compile
(
r'Password:?\s*$|passwd:?\s*$|密\s*码:?\s*$'
,
re
.
I
)
self
.
success_pattern
=
re
.
compile
(
r'Last\s*login|success|成功|#|\$'
,
re
.
I
)
if
config
.
TELNET_REGEX
:
try
:
self
.
custom_success_pattern
=
re
.
compile
(
r'{}'
.
format
(
config
.
TELNET_REGEX
),
re
.
I
)
except
(
TypeError
,
ValueError
):
pass
def
get_socket
(
self
):
logger
.
debug
(
'Get telnet server socket. {}'
.
format
(
self
.
client
.
user
))
...
...
@@ -269,7 +276,10 @@ class TelnetConnection:
logger
.
debug
(
b
'[Password prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
self
.
sock
.
send
(
self
.
system_user
.
password
.
encode
(
'utf-8'
)
+
b
'
\r\n
'
)
return
None
elif
self
.
success_pattern
.
search
(
data
):
elif
self
.
success_pattern
.
search
(
data
)
or
\
(
self
.
custom_success_pattern
and
self
.
custom_success_pattern
.
search
(
data
)):
self
.
client
.
send
(
raw_data
)
logger
.
debug
(
b
'[Login Success prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
True
else
:
...
...
coco/interactive.py
View file @
c3af966b
...
...
@@ -80,9 +80,11 @@ class InteractiveServer:
#
def
display_banner
(
self
):
default_title
=
_
(
'Welcome to use Jumpserver open source fortress system'
)
header_title
=
config
.
get
(
'HEADER_TITLE'
)
or
default_title
self
.
client
.
send
(
char
.
CLEAR_CHAR
)
self
.
display_logo
()
header
=
_
(
"
\n
{T}{T}{title} {user},
Welcome to use Jumpserver open source fortress system
{end}{R}{R}"
)
header
=
_
(
"
\n
{T}{T}{title} {user},
{header_title}
{end}{R}{R}"
)
menu
=
[
_
(
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, Comment{end} to search login(if unique).{R}"
),
_
(
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} search, such as: /ip.{R}"
),
...
...
@@ -95,7 +97,8 @@ class InteractiveServer:
_
(
"{T}0) Enter {green}q{end} exit.{R}"
)
]
self
.
client
.
send_unicode
(
header
.
format
(
title
=
"
\033
[1;32m"
,
user
=
self
.
client
.
user
,
end
=
"
\033
[0m"
,
title
=
"
\033
[1;32m"
,
user
=
self
.
client
.
user
,
header_title
=
header_title
,
end
=
"
\033
[0m"
,
T
=
'
\t
'
,
R
=
'
\r\n\r
'
))
for
item
in
menu
:
...
...
@@ -132,7 +135,6 @@ class InteractiveServer:
self
.
display_banner
()
elif
opt
in
[
'r'
,
'R'
]:
self
.
refresh_assets_nodes
()
self
.
display_banner
()
elif
opt
in
[
'h'
,
'H'
]:
self
.
display_banner
()
else
:
...
...
@@ -162,8 +164,9 @@ class InteractiveServer:
self
.
display_assets_paging
(
assets
)
def
refresh_assets_nodes
(
self
):
self
.
get_user_assets_and_update_async
()
self
.
get_user_nodes_async
()
self
.
get_user_assets_and_update
(
cache_policy
=
'2'
)
self
.
get_user_nodes
(
cache_policy
=
'2'
)
self
.
client
.
send_unicode
(
_
(
"Refresh done"
))
def
wait_until_assets_load
(
self
):
while
self
.
assets
is
None
and
\
...
...
@@ -316,9 +319,7 @@ class InteractiveServer:
#
def
load_user_assets_from_cache
(
self
):
assets
=
self
.
__class__
.
_user_assets_cached
.
get
(
self
.
client
.
user
.
id
)
assets
=
self
.
__class__
.
_user_assets_cached
.
get
(
self
.
client
.
user
.
id
)
self
.
assets
=
assets
if
assets
:
self
.
total_asset_count
=
len
(
assets
)
...
...
@@ -327,8 +328,8 @@ class InteractiveServer:
thread
=
threading
.
Thread
(
target
=
self
.
get_user_assets_and_update
)
thread
.
start
()
def
get_user_assets_and_update
(
self
):
assets
=
app_service
.
get_user_assets
(
self
.
client
.
user
)
def
get_user_assets_and_update
(
self
,
cache_policy
=
'1'
):
assets
=
app_service
.
get_user_assets
(
self
.
client
.
user
,
cache_policy
=
cache_policy
)
assets
=
self
.
filter_system_users
(
assets
)
self
.
__class__
.
_user_assets_cached
[
self
.
client
.
user
.
id
]
=
assets
self
.
load_user_assets_from_cache
()
...
...
@@ -341,8 +342,8 @@ class InteractiveServer:
thread
=
threading
.
Thread
(
target
=
self
.
get_user_nodes
)
thread
.
start
()
def
get_user_nodes
(
self
):
nodes
=
app_service
.
get_user_asset_groups
(
self
.
client
.
user
)
def
get_user_nodes
(
self
,
cache_policy
=
'1'
):
nodes
=
app_service
.
get_user_asset_groups
(
self
.
client
.
user
,
cache_policy
=
cache_policy
)
nodes
=
sorted
(
nodes
,
key
=
lambda
node
:
node
.
key
)
self
.
nodes
=
self
.
filter_system_users_of_assets_under_nodes
(
nodes
)
self
.
_construct_node_tree
()
...
...
coco/logger.py
View file @
c3af966b
...
...
@@ -45,12 +45,11 @@ def create_logger():
},
'file'
:
{
'level'
:
'DEBUG'
,
'class'
:
'logging.handlers.
Timed
RotatingFileHandler'
,
'class'
:
'logging.handlers.RotatingFileHandler'
,
'formatter'
:
'main'
,
'filename'
:
log_path
,
'when'
:
"D"
,
'interval'
:
1
,
"backupCount"
:
7
'maxBytes'
:
1024
*
1024
*
100
,
'backupCount'
:
7
,
},
},
loggers
=
{
...
...
coco/models.py
View file @
c3af966b
...
...
@@ -385,6 +385,9 @@ class TelnetServer(BaseServer):
""" self.chan: socket object """
return
getattr
(
self
.
chan
,
'_closed'
,
False
)
def
resize_pty
(
self
):
pass
class
Server
(
BaseServer
):
"""
...
...
coco/proxy.py
View file @
c3af966b
...
...
@@ -72,6 +72,12 @@ class ProxyServer:
self
.
server
.
close
()
return
session
=
Session
.
new_session
(
self
.
client
,
self
.
server
)
if
not
session
:
msg
=
_
(
"Connect with api server failed"
)
logger
.
error
(
msg
)
self
.
client
.
send_unicode
(
msg
)
self
.
server
.
close
()
try
:
session
.
bridge
()
finally
:
...
...
coco/recorder.py
View file @
c3af966b
...
...
@@ -3,16 +3,16 @@
#
import
threading
import
datetime
import
time
import
os
import
gzip
import
json
from
copy
import
deepcopy
import
jms_storage
from
.conf
import
config
from
.utils
import
get_logger
from
.utils
import
get_logger
,
gzip_file
from
.struct
import
MemoryQueue
from
.service
import
app_service
...
...
@@ -22,14 +22,23 @@ BUF_SIZE = 1024
class
ReplayRecorder
(
object
):
time_start
=
None
target
=
None
storage
=
None
session_id
=
None
filename
=
None
file
=
None
file_path
=
None
filename_gz
=
None
file_gz_path
=
None
def
__init__
(
self
):
super
(
ReplayRecorder
,
self
)
.
__init__
()
self
.
file
=
None
self
.
file_path
=
None
self
.
get_storage
()
def
get_storage
(
self
):
conf
=
deepcopy
(
config
[
"REPLAY_STORAGE"
])
conf
[
"SERVICE"
]
=
app_service
self
.
storage
=
jms_storage
.
get_object_storage
(
conf
)
def
record
(
self
,
data
):
"""
:param data:
...
...
@@ -47,49 +56,63 @@ class ReplayRecorder(object):
def
session_start
(
self
,
session_id
):
self
.
time_start
=
time
.
time
()
filename
=
session_id
+
'.replay.gz'
self
.
file_path
=
os
.
path
.
join
(
config
[
'LOG_DIR'
],
filename
)
self
.
file
=
gzip
.
open
(
self
.
file_path
,
'at'
)
self
.
session_id
=
session_id
self
.
filename
=
session_id
self
.
filename_gz
=
session_id
+
'.replay.gz'
date
=
datetime
.
datetime
.
utcnow
()
.
strftime
(
'
%
Y-
%
m-
%
d'
)
replay_dir
=
os
.
path
.
join
(
config
.
REPLAY_DIR
,
date
)
if
not
os
.
path
.
isdir
(
replay_dir
):
os
.
makedirs
(
replay_dir
,
exist_ok
=
True
)
# 录像记录路径
self
.
file_path
=
os
.
path
.
join
(
replay_dir
,
self
.
filename
)
# 录像压缩到的路径
self
.
file_gz_path
=
os
.
path
.
join
(
replay_dir
,
self
.
filename_gz
)
# 录像上传上去的路径
self
.
target
=
date
+
'/'
+
self
.
filename_gz
self
.
file
=
open
(
self
.
file_path
,
'at'
)
self
.
file
.
write
(
'{'
)
def
session_end
(
self
,
session_id
):
self
.
file
.
write
(
'"0":""}'
)
self
.
file
.
close
()
self
.
upload_replay
(
session_id
)
gzip_file
(
self
.
file_path
,
self
.
file_gz_path
)
self
.
upload_replay_some_times
()
def
get_storage
(
self
):
conf
=
deepcopy
(
config
[
"REPLAY_STORAGE"
])
conf
[
"SERVICE"
]
=
app_service
self
.
storage
=
jms_storage
.
get_object_storage
(
conf
)
def
upload_replay
(
self
,
session_id
,
times
=
3
):
def
upload_replay_some_times
(
self
,
times
=
3
):
# 如果上传OSS、S3失败则尝试上传到服务器
if
times
<
1
:
if
self
.
storage
.
type
==
'jms'
:
return
False
else
:
self
.
storage
=
jms_storage
.
JMSReplayStorage
(
{
"SERVICE"
:
app_service
}
)
self
.
upload_replay
(
session_id
,
times
=
3
)
self
.
storage
=
jms_storage
.
JMSReplayStorage
(
{
"SERVICE"
:
app_service
}
)
self
.
upload_replay_some_times
(
times
=
3
)
ok
,
msg
=
self
.
push_to_storage
(
session_id
)
ok
,
msg
=
self
.
upload_replay
(
)
if
not
ok
:
msg
=
'Failed push replay file {}: {}, try again {}'
.
format
(
se
ssion_id
,
msg
,
times
se
lf
.
filename
,
msg
,
times
)
logger
.
warn
(
msg
)
self
.
upload_replay
(
session_id
,
times
-
1
)
self
.
upload_replay
_some_times
(
times
-
1
)
else
:
msg
=
'Success push replay file: {}'
.
format
(
session_id
)
msg
=
'Success push replay file: {}'
.
format
(
se
lf
.
se
ssion_id
)
logger
.
debug
(
msg
)
self
.
finish_replay
(
3
,
session_id
)
os
.
unlink
(
self
.
file_path
)
return
True
def
push_to_storage
(
self
,
session_id
):
dt
=
time
.
strftime
(
'
%
Y-
%
m-
%
d'
,
time
.
localtime
(
self
.
time_start
))
target
=
dt
+
'/'
+
session_id
+
'.replay.gz'
return
self
.
storage
.
upload
(
self
.
file_path
,
target
)
def
upload_replay
(
self
):
# 如果文件为空就直接删除
if
not
os
.
path
.
isfile
(
self
.
file_gz_path
):
return
False
,
'Not found the file: {}'
.
format
(
self
.
file_gz_path
)
if
os
.
path
.
getsize
(
self
.
file_gz_path
)
==
0
:
os
.
unlink
(
self
.
file_gz_path
)
return
True
,
''
ok
,
msg
=
self
.
storage
.
upload
(
self
.
file_gz_path
,
self
.
target
)
if
ok
:
self
.
finish_replay
(
3
,
self
.
session_id
)
os
.
unlink
(
self
.
file_gz_path
)
return
ok
,
msg
def
finish_replay
(
self
,
times
,
session_id
):
if
times
<
1
:
...
...
@@ -147,9 +170,10 @@ class CommandRecorder(object):
if
not
data_set
:
continue
logger
.
debug
(
"Send {} commands to server"
.
format
(
len
(
data_set
)))
ok
=
self
.
storage
.
bulk_save
(
data_set
)
if
not
ok
:
self
.
queue
.
mput
(
data_set
)
for
i
in
range
(
5
):
ok
=
self
.
storage
.
bulk_save
(
data_set
)
if
ok
:
break
thread
=
threading
.
Thread
(
target
=
func
)
thread
.
daemon
=
True
...
...
coco/session.py
View file @
c3af966b
...
...
@@ -48,7 +48,14 @@ class Session:
session
.
set_command_recorder
(
command_recorder
)
session
.
set_replay_recorder
(
replay_recorder
)
cls
.
sessions
[
session
.
id
]
=
session
app_service
.
create_session
(
session
.
to_json
())
_session
=
None
for
i
in
range
(
5
):
_session
=
app_service
.
create_session
(
session
.
to_json
())
if
_session
:
break
time
.
sleep
(
0.2
)
if
_session
is
None
:
return
None
return
session
@classmethod
...
...
coco/sftp.py
View file @
c3af966b
...
...
@@ -45,7 +45,8 @@ def convert_error(func):
class
SFTPServer
(
paramiko
.
SFTPServerInterface
):
root
=
config
.
SFTP_ROOT
# Home or /tmp or other path, must exist on all server
# Home or /tmp or other path, must exist on all server
root
=
config
.
SFTP_ROOT
def
__init__
(
self
,
server
,
**
kwargs
):
"""
...
...
@@ -234,6 +235,9 @@ class SFTPServer(paramiko.SFTPServerInterface):
else
:
client
,
rpath
=
self
.
get_sftp_client_rpath
(
request
)
output
=
client
.
listdir_attr
(
rpath
)
show_hidden_file
=
config
[
'SFTP_SHOW_HIDDEN_FILE'
]
if
not
show_hidden_file
:
output
=
[
attr
for
attr
in
output
if
not
attr
.
filename
.
startswith
(
'.'
)]
return
output
@convert_error
...
...
@@ -291,6 +295,7 @@ class SFTPServer(paramiko.SFTPServerInterface):
try
:
client
,
rpath
=
self
.
get_sftp_client_rpath
(
path
)
f
=
client
.
open
(
rpath
,
mode
,
bufsize
=
4096
)
f
.
prefetch
()
obj
=
paramiko
.
SFTPHandle
(
flags
)
obj
.
filename
=
rpath
obj
.
readfile
=
f
...
...
coco/utils.py
View file @
c3af966b
...
...
@@ -8,6 +8,7 @@ import logging
import
re
import
os
import
gettext
import
gzip
from
io
import
StringIO
from
binascii
import
hexlify
from
werkzeug.local
import
Local
,
LocalProxy
...
...
@@ -464,4 +465,11 @@ def ignore_error(func):
return
wrapper
def
gzip_file
(
src_path
,
dst_path
,
unlink_ori
=
True
):
with
open
(
src_path
,
'rt'
)
as
src
,
gzip
.
open
(
dst_path
,
'at'
)
as
dst
:
dst
.
writelines
(
src
)
if
unlink_ori
:
os
.
unlink
(
src_path
)
ugettext
=
LocalProxy
(
partial
(
_find
,
'LANGUAGE_CODE'
))
cocod
View file @
c3af966b
...
...
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
#
import
os
import
subprocess
if
os
.
environ
.
get
(
'USE_EVENTLET'
,
'1'
)
==
'1'
:
import
eventlet
...
...
@@ -17,13 +18,30 @@ import argparse
import
time
import
signal
BASE_DIR
=
os
.
path
.
abspath
(
os
.
path
.
dirname
(
__file__
))
sys
.
path
.
insert
(
0
,
BASE_DIR
)
dirs
=
(
'logs'
,
'keys'
)
for
d
in
dirs
:
d2
=
os
.
path
.
join
(
'data'
,
d
)
if
not
os
.
path
.
isdir
(
d2
):
os
.
makedirs
(
d2
)
from
coco
import
Coco
try
:
from
coco
import
Coco
except
ImportError
as
e
:
print
(
"Import error: {}"
.
format
(
e
))
print
(
"Sys path: {}"
.
format
(
sys
.
path
))
print
(
"Python is: "
)
print
(
subprocess
.
call
(
'which python'
,
shell
=
True
))
try
:
import
coco
print
(
"Coco is: {}"
.
format
(
coco
))
print
(
"Coco dir: {}"
.
format
(
os
.
listdir
(
"coco"
)))
except
:
pass
raise
try
:
from
coco.conf
import
config
...
...
config_example.yml
View file @
c3af966b
...
...
@@ -27,14 +27,14 @@ BOOTSTRAP_TOKEN: <PleasgeChangeSameWithJumpserver>
# 加密密钥
# SECRET_KEY: null
# 设置日志级别 [
'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL', 'CRITICAL'
]
# 设置日志级别 [
DEBUG, INFO, WARN, ERROR, FATAL, CRITICAL
]
# LOG_LEVEL: INFO
# 日志存放的目录
# LOG_DIR: logs
# SSH白名单
# ALLOW_SSH_USER:
'all'
# ALLOW_SSH_USER:
all
# SSH黑名单, 如果用户同时在白名单和黑名单,黑名单优先生效
# BLOCK_SSH_USER:
...
...
@@ -49,5 +49,11 @@ BOOTSTRAP_TOKEN: <PleasgeChangeSameWithJumpserver>
# SSH连接超时时间 (default 15 seconds)
# SSH_TIMEOUT: 15
# 语言
= en
# 语言
[en,zh]
# LANGUAGE_CODE: zh
# SFTP的根目录, 可选 /tmp, Home其他自定义目录
# SFTP_ROOT: /tmp
# SFTP是否显示隐藏文件
# SFTP_SHOW_HIDDEN_FILE: false
entrypoint.sh
0 → 100755
View file @
c3af966b
#!/bin/bash
function
cleanup
()
{
local
pids
=
`
jobs
-p
`
if
[[
"
${
pids
}
"
!=
""
]]
;
then
kill
${
pids
}
>
/dev/null 2>/dev/null
fi
}
trap
cleanup EXIT
if
[[
"
$1
"
==
"bash"
]]
;
then
bash
else
python cocod start
fi
locale/en/LC_MESSAGES/coco.mo
View file @
c3af966b
No preview for this file type
locale/en/LC_MESSAGES/coco.po
View file @
c3af966b
...
...
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 201
8-12-21 16:48
+0800\n"
"POT-Creation-Date: 201
9-03-06 14:51
+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,159 +16,170 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:1
45
#: coco/app.py:1
82
msgid "Connect idle more than {} minutes, disconnect"
msgstr ""
#: coco/interactive.py:84
#: coco/interactive.py:83
msgid "Welcome to use Jumpserver open source fortress system"
msgstr ""
#: coco/interactive.py:87
#, python-brace-format
msgid ""
"\n"
"{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system "
"{end}{R}{R}"
"{T}{T}{title} {user}, {header_title} {end}{R}{R}"
msgstr ""
#: coco/interactive.py:8
6
#: coco/interactive.py:8
9
#, 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:
87
#: coco/interactive.py:
90
#, 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:
88
#: coco/interactive.py:
91
#, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr ""
#: coco/interactive.py:
89
#: coco/interactive.py:
92
#, python-brace-format
msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr ""
#: coco/interactive.py:9
0
#: coco/interactive.py:9
3
#, 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:9
1
#: coco/interactive.py:9
4
#, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr ""
#: coco/interactive.py:9
2
#: coco/interactive.py:9
5
#, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr ""
#: coco/interactive.py:9
3
#: coco/interactive.py:9
6
#, python-brace-format
msgid "{T}8) Enter {green}r{end} to refresh your assets and nodes.{R}"
msgstr ""
#: coco/interactive.py:9
4
#: coco/interactive.py:9
7
#, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr ""
#: coco/interactive.py:15
5
#: coco/interactive.py:15
8
msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr ""
#: coco/interactive.py:217
#: coco/interactive.py:169
msgid "Refresh done"
msgstr ""
#: coco/interactive.py:211
msgid "No Assets"
msgstr ""
#: coco/interactive.py:2
80
msgid "
Tips: Enter the asset ID and log directly into the asset.
"
#: coco/interactive.py:2
66
msgid "
ID
"
msgstr ""
#: coco/interactive.py:2
81
msgid "
Page up: P/p
"
#: coco/interactive.py:2
66
msgid "
Hostname
"
msgstr ""
#: coco/interactive.py:2
82
msgid "
Page down: Enter|N/n
"
#: coco/interactive.py:2
66
msgid "
IP
"
msgstr ""
#: coco/interactive.py:2
83
msgid "
BACK: b/q
"
#: coco/interactive.py:2
66
msgid "
LoginAs
"
msgstr ""
#: coco/interactive.py:
303
msgid "
ID
"
#: coco/interactive.py:
280
msgid "
Comment
"
msgstr ""
#: coco/interactive.py:
303
msgid "
Hostname
"
#: coco/interactive.py:
290
msgid "
Page: {}, Count: {}, Total Page: {}, Total Count: {}
"
msgstr ""
#: coco/interactive.py:
303
msgid "
IP
"
#: coco/interactive.py:
296
msgid "
Tips: Enter the asset ID and log directly into the asset.
"
msgstr ""
#: coco/interactive.py:
303
msgid "
LoginAs
"
#: coco/interactive.py:
298
msgid "
Page up: P/p
"
msgstr ""
#: coco/interactive.py:
317
msgid "
Comment
"
#: coco/interactive.py:
299
msgid "
Page down: Enter|N/n
"
msgstr ""
#: coco/interactive.py:3
26
msgid "
Page: {}, Count: {}, Total Page: {}, Total Count: {}
"
#: coco/interactive.py:3
00
msgid "
BACK: b/q
"
msgstr ""
#: coco/interactive.py:3
98
#: coco/interactive.py:3
71
msgid "No Nodes"
msgstr ""
#: coco/interactive.py:
402
#: coco/interactive.py:
375
msgid "Node: [ ID.Name(Asset amount) ]"
msgstr ""
#: coco/interactive.py:
404
#: coco/interactive.py:
377
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr ""
#: coco/interactive.py:
412
#: coco/interactive.py:
385
msgid "There is no matched node, please re-enter"
msgstr ""
#: coco/interactive.py:4
42
#: coco/interactive.py:4
15
msgid "Select a login:: "
msgstr ""
#: coco/interactive.py:4
65
#: coco/interactive.py:4
38
msgid "No system user"
msgstr ""
#: coco/models.py:2
4
2
#: coco/models.py:2
5
2
msgid ""
"Warning: Failed to load filter rule, please press Ctrl + D to exit retry."
msgstr ""
#: coco/models.py:2
5
1
#: coco/models.py:2
6
1
msgid "Command `{}` is forbidden ........"
msgstr ""
#: coco/proxy.py:89
#: coco/proxy.py:76
msgid "Connect with api server failed"
msgstr ""
#: coco/proxy.py:104
msgid "No permission"
msgstr ""
#: coco/proxy.py:1
31
#: coco/proxy.py:1
47
msgid "Connecting to {}@{} {:.1f}"
msgstr ""
#: coco/session.py:1
43
#: coco/session.py:1
54
msgid "Terminated by administrator"
msgstr ""
locale/zh_CN/LC_MESSAGES/coco.mo
View file @
c3af966b
No preview for this file type
locale/zh_CN/LC_MESSAGES/coco.po
View file @
c3af966b
...
...
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 201
8-12-21 16:48
+0800\n"
"POT-Creation-Date: 201
9-03-06 14:51
+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,21 +16,24 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:1
45
#: coco/app.py:1
82
msgid "Connect idle more than {} minutes, disconnect"
msgstr "空闲时间超过 {} 分钟,断开连接"
#: coco/interactive.py:84
#: coco/interactive.py:83
msgid "Welcome to use Jumpserver open source fortress system"
msgstr "欢迎使用Jumpserver开源跳板机系统"
#: coco/interactive.py:87
#, python-brace-format
msgid ""
"\n"
"{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system "
"{end}{R}{R}"
"{T}{T}{title} {user}, {header_title} {end}{R}{R}"
msgstr ""
"\n"
"{T}{T}{title} {user},
欢迎使用Jumpserver开源跳板机系统
{end}{R}{R}"
"{T}{T}{title} {user},
{header_title}
{end}{R}{R}"
#: coco/interactive.py:8
6
#: coco/interactive.py:8
9
#, python-brace-format
msgid ""
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, "
...
...
@@ -39,7 +42,7 @@ msgstr ""
"{T}1) 输入 {green}ID{end} 直接登录 或 输入{green}部分 IP,主机名,备注{end} 进"
"行搜索登录(如果唯一).{R}"
#: coco/interactive.py:
87
#: coco/interactive.py:
90
#, python-brace-format
msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
...
...
@@ -48,18 +51,18 @@ msgstr ""
"{T}2) 输入 {green}/{end} + {green}IP, 主机名{end} or {green}备注 {end}搜索. "
"如: /ip{R}"
#: coco/interactive.py:
88
#: coco/interactive.py:
91
#, 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:
89
#: coco/interactive.py:
92
#, 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:9
0
#: coco/interactive.py:9
3
#, python-brace-format
msgid ""
"{T}5) Enter {green}g{end} + {green}NodeID{end} to display the host under the "
...
...
@@ -67,116 +70,124 @@ msgid ""
msgstr ""
"{T}5) 输入 {green}g{end} + {green}节点ID{end} 显示节点下主机. 如: g1{R}"
#: coco/interactive.py:9
1
#: coco/interactive.py:9
4
#, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr "{T}6) 输入 {green}s{end} 中/英文切换.{R}"
#: coco/interactive.py:9
2
#: coco/interactive.py:9
5
#, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr "{T}7) 输入 {green}h{end} 帮助.{R}"
#: coco/interactive.py:9
3
#: coco/interactive.py:9
6
#, 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
4
#: coco/interactive.py:9
7
#, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr "{T}0) 输入 {green}q{end} 退出.{R}"
#: coco/interactive.py:15
5
#: coco/interactive.py:15
8
msgid "Terminal does not support login rdp, please use web terminal to access"
msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:217
#: coco/interactive.py:169
msgid "Refresh done"
msgstr "刷新完成"
#: coco/interactive.py:211
msgid "No Assets"
msgstr "没有资产"
#: coco/interactive.py:280
msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr "提示: 输入资产ID,直接登录资产."
#: coco/interactive.py:281
msgid "Page up: P/p"
msgstr "上一页: P/p"
#: coco/interactive.py:282
msgid "Page down: Enter|N/n"
msgstr "下一页: Enter|N/n"
#: coco/interactive.py:283
msgid "BACK: b/q"
msgstr "返回: B/b"
#: coco/interactive.py:303
#: coco/interactive.py:266
msgid "ID"
msgstr ""
#: coco/interactive.py:
303
#: coco/interactive.py:
266
msgid "Hostname"
msgstr "主机名"
#: coco/interactive.py:
303
#: coco/interactive.py:
266
msgid "IP"
msgstr ""
#: coco/interactive.py:
303
#: coco/interactive.py:
266
msgid "LoginAs"
msgstr "登录用户"
#: coco/interactive.py:
317
#: coco/interactive.py:
280
msgid "Comment"
msgstr "备注"
#: coco/interactive.py:
326
#: coco/interactive.py:
290
msgid "Page: {}, Count: {}, Total Page: {}, Total Count: {}"
msgstr "页码: {}, 数量: {}, 总页数: {}, 总数量: {}"
#: coco/interactive.py:398
#: coco/interactive.py:296
msgid "Tips: Enter the asset ID and log directly into the asset."
msgstr "提示: 输入资产ID,直接登录资产."
#: coco/interactive.py:298
msgid "Page up: P/p"
msgstr "上一页: P/p"
#: coco/interactive.py:299
msgid "Page down: Enter|N/n"
msgstr "下一页: Enter|N/n"
#: coco/interactive.py:300
msgid "BACK: b/q"
msgstr "返回: B/b"
#: coco/interactive.py:371
msgid "No Nodes"
msgstr "没有节点"
#: coco/interactive.py:
402
#: coco/interactive.py:
375
msgid "Node: [ ID.Name(Asset amount) ]"
msgstr "节点: [ ID.名称(资产数量) ]"
#: coco/interactive.py:
404
#: coco/interactive.py:
377
msgid "Tips: Enter g+NodeID to display the host under the node, such as g1"
msgstr "提示: 输入 g+节点ID 显示节点下主机. 如: g1"
#: coco/interactive.py:
412
#: coco/interactive.py:
385
msgid "There is no matched node, please re-enter"
msgstr "没有匹配分组,请重新输入"
#: coco/interactive.py:4
42
#: coco/interactive.py:4
15
msgid "Select a login:: "
msgstr "选择一个登录:"
#: coco/interactive.py:4
65
#: coco/interactive.py:4
38
msgid "No system user"
msgstr "没有系统用户"
#: coco/models.py:2
4
2
#: coco/models.py:2
5
2
msgid ""
"Warning: Failed to load filter rule, please press Ctrl + D to exit retry."
msgstr "警告: 加载过滤规则失败,请按 Ctrl + D 退出重试."
#: coco/models.py:2
5
1
#: coco/models.py:2
6
1
msgid "Command `{}` is forbidden ........"
msgstr "命令 `{}` 是被禁止的 ..."
#: coco/proxy.py:89
#: coco/proxy.py:76
msgid "Connect with api server failed"
msgstr ""
#: coco/proxy.py:104
msgid "No permission"
msgstr "没有权限"
#: coco/proxy.py:1
31
#: coco/proxy.py:1
47
msgid "Connecting to {}@{} {:.1f}"
msgstr "开始连接到 {}@{} {:.1f}"
#: coco/session.py:1
43
#: coco/session.py:1
54
msgid "Terminated by administrator"
msgstr "被管理员中断"
...
...
requirements/requirements.txt
View file @
c3af966b
...
...
@@ -18,7 +18,7 @@ idna==2.6
itsdangerous==0.24
Jinja2==2.10
jmespath==0.9.3
jms-storage==0.0.2
0
jms-storage==0.0.2
2
jumpserver-python-sdk==0.0.56
MarkupSafe==1.0
oss2==2.4.0
...
...
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