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
8a60c4eb
Unverified
Commit
8a60c4eb
authored
Sep 04, 2018
by
老广
Committed by
GitHub
Sep 04, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #95 from jumpserver/dev
Dev
parents
a2ec9e77
4b742525
Show whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
1470 additions
and
833 deletions
+1470
-833
app.py
coco/app.py
+56
-133
config.py
coco/config.py
+46
-2
connection.py
coco/connection.py
+9
-5
ctx.py
coco/ctx.py
+6
-5
httpd.py
coco/httpd.py
+93
-104
interactive.py
coco/interactive.py
+59
-46
interface.py
coco/interface.py
+89
-47
logger.py
coco/logger.py
+4
-1
models.py
coco/models.py
+179
-284
proxy.py
coco/proxy.py
+63
-86
recorder.py
coco/recorder.py
+23
-11
service.py
coco/service.py
+9
-0
session.py
coco/session.py
+69
-35
sftp.py
coco/sftp.py
+2
-2
sshd.py
coco/sshd.py
+40
-35
struct.py
coco/struct.py
+26
-10
tasks.py
coco/tasks.py
+3
-7
utils.py
coco/utils.py
+66
-17
conf_example.py
conf_example.py
+12
-0
coco.mo
locale/en/LC_MESSAGES/coco.mo
+0
-0
coco.po
locale/en/LC_MESSAGES/coco.po
+145
-0
coco.po~
locale/en/LC_MESSAGES/coco.po~
+137
-0
coco.mo
locale/zh_CN/LC_MESSAGES/coco.mo
+0
-0
coco.po
locale/zh_CN/LC_MESSAGES/coco.po
+151
-0
coco.po~
locale/zh_CN/LC_MESSAGES/coco.po~
+143
-0
requirements.txt
requirements/requirements.txt
+1
-1
rpm_requirements.txt
requirements/rpm_requirements.txt
+1
-2
messages.sh
utils/messages.sh
+38
-0
No files found.
coco/app.py
View file @
8a60c4eb
...
@@ -2,66 +2,40 @@
...
@@ -2,66 +2,40 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
import
eventlet
from
eventlet.debug
import
hub_prevent_multiple_readers
eventlet
.
monkey_patch
()
hub_prevent_multiple_readers
(
False
)
import
datetime
import
datetime
import
os
import
os
import
time
import
time
import
threading
import
threading
import
socket
import
json
import
json
import
signal
import
signal
import
eventlet
from
eventlet.debug
import
hub_prevent_multiple_readers
from
jms.service
import
AppService
from
.config
import
C
onfig
from
.config
import
c
onfig
from
.sshd
import
SSHServer
from
.sshd
import
SSHServer
from
.httpd
import
HttpServer
from
.httpd
import
HttpServer
from
.logger
import
create_logger
from
.logger
import
create_logger
from
.tasks
import
TaskHandler
from
.tasks
import
TaskHandler
from
.recorder
import
ReplayRecorder
,
CommandRecorder
from
.utils
import
get_logger
,
ugettext
as
_
,
\
from
.utils
import
get_logger
,
register_app
,
register_service
ignore_error
from
.service
import
init_app
from
.ctx
import
app_service
from
.recorder
import
get_replay_recorder
from
.session
import
Session
from
.models
import
Connection
eventlet
.
monkey_patch
()
hub_prevent_multiple_readers
(
False
)
__version__
=
'1.
3.3
'
__version__
=
'1.
4.1
'
BASE_DIR
=
os
.
path
.
dirname
(
os
.
path
.
dirname
(
__file__
))
BASE_DIR
=
os
.
path
.
dirname
(
os
.
path
.
dirname
(
__file__
))
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
class
Coco
:
class
Coco
:
config_class
=
Config
def
__init__
(
self
):
default_config
=
{
'DEFAULT_NAME'
:
socket
.
gethostname
(),
'NAME'
:
None
,
'CORE_HOST'
:
'http://127.0.0.1:8080'
,
'DEBUG'
:
True
,
'BIND_HOST'
:
'0.0.0.0'
,
'SSHD_PORT'
:
2222
,
'HTTPD_PORT'
:
5000
,
'ACCESS_KEY'
:
''
,
'ACCESS_KEY_ENV'
:
'COCO_ACCESS_KEY'
,
'ACCESS_KEY_FILE'
:
os
.
path
.
join
(
BASE_DIR
,
'keys'
,
'.access_key'
),
'SECRET_KEY'
:
None
,
'LOG_LEVEL'
:
'DEBUG'
,
'LOG_DIR'
:
os
.
path
.
join
(
BASE_DIR
,
'logs'
),
'SESSION_DIR'
:
os
.
path
.
join
(
BASE_DIR
,
'sessions'
),
'ASSET_LIST_SORT_BY'
:
'hostname'
,
# hostname, ip
'PASSWORD_AUTH'
:
True
,
'PUBLIC_KEY_AUTH'
:
True
,
'HEARTBEAT_INTERVAL'
:
5
,
'MAX_CONNECTIONS'
:
500
,
'ADMINS'
:
''
,
'COMMAND_STORAGE'
:
{
'TYPE'
:
'server'
},
# server
'REPLAY_STORAGE'
:
{
'TYPE'
:
'server'
},
}
def
__init__
(
self
,
root_path
=
None
):
self
.
root_path
=
root_path
if
root_path
else
BASE_DIR
self
.
sessions
=
[]
self
.
clients
=
[]
self
.
lock
=
threading
.
Lock
()
self
.
lock
=
threading
.
Lock
()
self
.
stop_evt
=
threading
.
Event
()
self
.
stop_evt
=
threading
.
Event
()
self
.
_service
=
None
self
.
_service
=
None
...
@@ -70,28 +44,8 @@ class Coco:
...
@@ -70,28 +44,8 @@ class Coco:
self
.
replay_recorder_class
=
None
self
.
replay_recorder_class
=
None
self
.
command_recorder_class
=
None
self
.
command_recorder_class
=
None
self
.
_task_handler
=
None
self
.
_task_handler
=
None
self
.
config
=
None
self
.
config
=
config
self
.
init_config
()
init_app
(
self
)
register_app
(
self
)
def
init_config
(
self
):
self
.
config
=
self
.
config_class
(
self
.
root_path
,
defaults
=
self
.
default_config
)
@property
def
name
(
self
):
if
self
.
config
[
'NAME'
]:
return
self
.
config
[
'NAME'
]
else
:
return
self
.
config
[
'DEFAULT_NAME'
]
@property
def
service
(
self
):
if
self
.
_service
is
None
:
self
.
_service
=
AppService
(
self
)
register_service
(
self
.
_service
)
return
self
.
_service
@property
@property
def
sshd
(
self
):
def
sshd
(
self
):
...
@@ -114,32 +68,26 @@ class Coco:
...
@@ -114,32 +68,26 @@ class Coco:
def
make_logger
(
self
):
def
make_logger
(
self
):
create_logger
(
self
)
create_logger
(
self
)
def
load_extra_conf_from_server
(
self
):
@staticmethod
configs
=
self
.
service
.
load_config_from_server
()
def
load_extra_conf_from_server
():
configs
=
app_service
.
load_config_from_server
()
logger
.
debug
(
"Loading config from server: {}"
.
format
(
logger
.
debug
(
"Loading config from server: {}"
.
format
(
json
.
dumps
(
configs
)
json
.
dumps
(
configs
)
))
))
self
.
config
.
update
(
configs
)
config
.
update
(
configs
)
@staticmethod
def
new_command_recorder
():
return
CommandRecorder
()
@staticmethod
def
new_replay_recorder
():
return
ReplayRecorder
()
def
bootstrap
(
self
):
def
bootstrap
(
self
):
self
.
make_logger
()
self
.
make_logger
()
self
.
service
.
initial
()
app_
service
.
initial
()
self
.
load_extra_conf_from_server
()
self
.
load_extra_conf_from_server
()
self
.
keep_heartbeat
()
self
.
keep_heartbeat
()
self
.
monitor_sessions
()
self
.
monitor_sessions
()
self
.
monitor_sessions_replay
()
self
.
monitor_sessions_replay
()
@ignore_error
def
heartbeat
(
self
):
def
heartbeat
(
self
):
_sessions
=
[
s
.
to_json
()
for
s
in
self
.
sessions
]
_sessions
=
[
s
.
to_json
()
for
s
in
Session
.
sessions
.
values
()
]
tasks
=
self
.
service
.
terminal_heartbeat
(
_sessions
)
tasks
=
app_
service
.
terminal_heartbeat
(
_sessions
)
if
tasks
:
if
tasks
:
self
.
handle_task
(
tasks
)
self
.
handle_task
(
tasks
)
if
tasks
is
False
:
if
tasks
is
False
:
...
@@ -159,19 +107,17 @@ class Coco:
...
@@ -159,19 +107,17 @@ class Coco:
def
func
():
def
func
():
while
not
self
.
stop_evt
.
is_set
():
while
not
self
.
stop_evt
.
is_set
():
self
.
heartbeat
()
self
.
heartbeat
()
time
.
sleep
(
self
.
config
[
"HEARTBEAT_INTERVAL"
])
time
.
sleep
(
config
[
"HEARTBEAT_INTERVAL"
])
thread
=
threading
.
Thread
(
target
=
func
)
thread
=
threading
.
Thread
(
target
=
func
)
thread
.
start
()
thread
.
start
()
def
monitor_sessions_replay
(
self
):
def
monitor_sessions_replay
(
self
):
interval
=
10
interval
=
10
recorder
=
self
.
new_replay_recorder
()
log_dir
=
os
.
path
.
join
(
config
[
'LOG_DIR'
])
log_dir
=
os
.
path
.
join
(
self
.
config
[
'LOG_DIR'
])
def
func
():
def
func
():
while
not
self
.
stop_evt
.
is_set
():
while
not
self
.
stop_evt
.
is_set
():
active_sessions
=
[
s
tr
(
session
.
id
)
for
session
in
self
.
sessions
]
active_sessions
=
[
s
id
for
sid
in
Session
.
sessions
]
for
filename
in
os
.
listdir
(
log_dir
):
for
filename
in
os
.
listdir
(
log_dir
):
session_id
=
filename
.
split
(
'.'
)[
0
]
session_id
=
filename
.
split
(
'.'
)[
0
]
full_path
=
os
.
path
.
join
(
log_dir
,
filename
)
full_path
=
os
.
path
.
join
(
log_dir
,
filename
)
...
@@ -179,6 +125,7 @@ class Coco:
...
@@ -179,6 +125,7 @@ class Coco:
if
len
(
session_id
)
!=
36
:
if
len
(
session_id
)
!=
36
:
continue
continue
recorder
=
get_replay_recorder
()
if
session_id
not
in
active_sessions
:
if
session_id
not
in
active_sessions
:
recorder
.
file_path
=
full_path
recorder
.
file_path
=
full_path
ok
=
recorder
.
upload_replay
(
session_id
,
1
)
ok
=
recorder
.
upload_replay
(
session_id
,
1
)
...
@@ -190,21 +137,33 @@ class Coco:
...
@@ -190,21 +137,33 @@ class Coco:
thread
.
start
()
thread
.
start
()
def
monitor_sessions
(
self
):
def
monitor_sessions
(
self
):
interval
=
self
.
config
[
"HEARTBEAT_INTERVAL"
]
interval
=
config
[
"HEARTBEAT_INTERVAL"
]
def
check_session_idle_too_long
(
s
):
delta
=
datetime
.
datetime
.
utcnow
()
-
s
.
date_last_active
max_idle_seconds
=
config
[
'SECURITY_MAX_IDLE_TIME'
]
*
60
if
delta
.
seconds
>
max_idle_seconds
:
msg
=
_
(
"Connect idle more than {} minutes, disconnect"
)
.
format
(
config
[
'SECURITY_MAX_IDLE_TIME'
]
)
s
.
terminate
(
msg
=
msg
)
return
True
def
func
():
def
func
():
while
not
self
.
stop_evt
.
is_set
():
while
not
self
.
stop_evt
.
is_set
():
for
s
in
self
.
sessions
:
sessions_copy
=
[
s
for
s
in
Session
.
sessions
.
values
()]
if
not
s
.
stop_evt
.
is_set
()
:
for
s
in
sessions_copy
:
continue
# Session 没有正常关闭,
if
s
.
date_end
is
None
:
if
s
.
closed_unexpected
:
self
.
remove_session
(
s
)
Session
.
remove_session
(
s
.
id
)
continue
continue
delta
=
datetime
.
datetime
.
now
()
-
s
.
date_end
# Session已正常关闭
if
delta
>
datetime
.
timedelta
(
seconds
=
interval
*
5
):
if
s
.
closed
:
self
.
remove_session
(
s
)
Session
.
remove_session
(
s
)
else
:
check_session_idle_too_long
(
s
)
time
.
sleep
(
interval
)
time
.
sleep
(
interval
)
thread
=
threading
.
Thread
(
target
=
func
)
thread
=
threading
.
Thread
(
target
=
func
)
thread
.
start
()
thread
.
start
()
...
@@ -215,10 +174,10 @@ class Coco:
...
@@ -215,10 +174,10 @@ class Coco:
print
(
'Quit the server with CONTROL-C.'
)
print
(
'Quit the server with CONTROL-C.'
)
try
:
try
:
if
self
.
config
[
"SSHD_PORT"
]
!=
0
:
if
config
[
"SSHD_PORT"
]
!=
0
:
self
.
run_sshd
()
self
.
run_sshd
()
if
self
.
config
[
'HTTPD_PORT'
]
!=
0
:
if
config
[
'HTTPD_PORT'
]
!=
0
:
self
.
run_httpd
()
self
.
run_httpd
()
signal
.
signal
(
signal
.
SIGTERM
,
lambda
x
,
y
:
self
.
shutdown
())
signal
.
signal
(
signal
.
SIGTERM
,
lambda
x
,
y
:
self
.
shutdown
())
...
@@ -228,7 +187,6 @@ class Coco:
...
@@ -228,7 +187,6 @@ class Coco:
break
break
time
.
sleep
(
3
)
time
.
sleep
(
3
)
except
KeyboardInterrupt
:
except
KeyboardInterrupt
:
self
.
stop_evt
.
set
()
self
.
shutdown
()
self
.
shutdown
()
def
run_sshd
(
self
):
def
run_sshd
(
self
):
...
@@ -242,46 +200,11 @@ class Coco:
...
@@ -242,46 +200,11 @@ class Coco:
thread
.
start
()
thread
.
start
()
def
shutdown
(
self
):
def
shutdown
(
self
):
for
client
in
self
.
clients
:
logger
.
info
(
"Grace shutdown the server"
)
self
.
remove_client
(
client
)
for
connection
in
Connection
.
connections
.
values
():
connection
.
close
()
time
.
sleep
(
1
)
time
.
sleep
(
1
)
self
.
heartbeat
()
self
.
heartbeat
()
self
.
stop_evt
.
set
()
self
.
stop_evt
.
set
()
self
.
sshd
.
shutdown
()
self
.
sshd
.
shutdown
()
self
.
httpd
.
shutdown
()
self
.
httpd
.
shutdown
()
logger
.
info
(
"Grace shutdown the server"
)
def
add_client
(
self
,
client
):
with
self
.
lock
:
self
.
clients
.
append
(
client
)
logger
.
info
(
"New client {} join, total {} now"
.
format
(
client
,
len
(
self
.
clients
)
)
)
def
remove_client
(
self
,
client
):
with
self
.
lock
:
try
:
self
.
clients
.
remove
(
client
)
logger
.
info
(
"Client {} leave, total {} now"
.
format
(
client
,
len
(
self
.
clients
)
)
)
client
.
close
()
except
:
pass
def
add_session
(
self
,
session
):
with
self
.
lock
:
self
.
sessions
.
append
(
session
)
self
.
service
.
create_session
(
session
.
to_json
())
def
remove_session
(
self
,
session
):
with
self
.
lock
:
try
:
logger
.
info
(
"Remove session: {}"
.
format
(
session
))
self
.
sessions
.
remove
(
session
)
self
.
service
.
finish_session
(
session
.
to_json
())
except
ValueError
:
msg
=
"Remove session: {} fail, maybe already removed"
logger
.
warning
(
msg
.
format
(
session
))
coco/config.py
View file @
8a60c4eb
...
@@ -17,10 +17,17 @@ import os
...
@@ -17,10 +17,17 @@ import os
import
types
import
types
import
errno
import
errno
import
json
import
json
import
socket
from
werkzeug.utils
import
import_string
from
werkzeug.utils
import
import_string
BASE_DIR
=
os
.
path
.
dirname
(
os
.
path
.
dirname
(
__file__
))
root_path
=
os
.
environ
.
get
(
"COCO_PATH"
)
if
not
root_path
:
root_path
=
BASE_DIR
class
ConfigAttribute
(
object
):
class
ConfigAttribute
(
object
):
"""Makes an attribute forward to the config"""
"""Makes an attribute forward to the config"""
...
@@ -233,7 +240,7 @@ class Config(dict):
...
@@ -233,7 +240,7 @@ class Config(dict):
The resulting dictionary `image_store_config` would look like::
The resulting dictionary `image_store_config` would look like::
{
{
'type': 'fs',
'type
s
': 'fs',
'path': '/var/app/images',
'path': '/var/app/images',
'base_url': 'http://img.website.com'
'base_url': 'http://img.website.com'
}
}
...
@@ -266,4 +273,41 @@ class Config(dict):
...
@@ -266,4 +273,41 @@ class Config(dict):
return
'<
%
s
%
s>'
%
(
self
.
__class__
.
__name__
,
dict
.
__repr__
(
self
))
return
'<
%
s
%
s>'
%
(
self
.
__class__
.
__name__
,
dict
.
__repr__
(
self
))
access_key_path
=
os
.
path
.
abspath
(
os
.
path
.
join
(
root_path
,
'keys'
,
'.access_key'
))
default_config
=
{
'NAME'
:
socket
.
gethostname
(),
'CORE_HOST'
:
'http://127.0.0.1:8080'
,
'ROOT_PATH'
:
root_path
,
'DEBUG'
:
True
,
'BIND_HOST'
:
'0.0.0.0'
,
'SSHD_PORT'
:
2222
,
'HTTPD_PORT'
:
5000
,
'COCO_ACCESS_KEY'
:
''
,
'ACCESS_KEY_FILE'
:
access_key_path
,
'SECRET_KEY'
:
'SDK29K03
%
MM0ksf'
,
'LOG_LEVEL'
:
'DEBUG'
,
'LOG_DIR'
:
os
.
path
.
join
(
root_path
,
'logs'
),
'SESSION_DIR'
:
os
.
path
.
join
(
root_path
,
'sessions'
),
'ASSET_LIST_SORT_BY'
:
'hostname'
,
# hostname, ip
'PASSWORD_AUTH'
:
True
,
'PUBLIC_KEY_AUTH'
:
True
,
'SSH_TIMEOUT'
:
10
,
'ALLOW_SSH_USER'
:
[],
'BLOCK_SSH_USER'
:
[],
'HEARTBEAT_INTERVAL'
:
5
,
'MAX_CONNECTIONS'
:
500
,
# Not use now
'ADMINS'
:
''
,
'COMMAND_STORAGE'
:
{
'TYPE'
:
'server'
},
# server
'REPLAY_STORAGE'
:
{
'TYPE'
:
'server'
},
'LANGUAGE_CODE'
:
'zh'
,
'SECURITY_MAX_IDLE_TIME'
:
60
,
}
config
=
Config
(
root_path
,
default_config
)
config
.
from_pyfile
(
'conf.py'
)
try
:
from
conf
import
config
as
_conf
config
.
from_object
(
_conf
)
except
ImportError
:
pass
coco/connection.py
View file @
8a60c4eb
...
@@ -11,10 +11,11 @@ import paramiko
...
@@ -11,10 +11,11 @@ import paramiko
from
paramiko.ssh_exception
import
SSHException
from
paramiko.ssh_exception
import
SSHException
from
.ctx
import
app_service
from
.ctx
import
app_service
from
.utils
import
get_logger
,
get_private_key_fingerprint
,
net_input
from
.config
import
config
from
.utils
import
get_logger
,
get_private_key_fingerprint
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
TIMEOUT
=
10
BUF_SIZE
=
1024
BUF_SIZE
=
1024
MANUAL_LOGIN
=
'manual'
MANUAL_LOGIN
=
'manual'
AUTO_LOGIN
=
'auto'
AUTO_LOGIN
=
'auto'
...
@@ -46,9 +47,12 @@ class SSHConnection:
...
@@ -46,9 +47,12 @@ class SSHConnection:
ssh
.
connect
(
ssh
.
connect
(
asset
.
ip
,
port
=
asset
.
port
,
username
=
system_user
.
username
,
asset
.
ip
,
port
=
asset
.
port
,
username
=
system_user
.
username
,
password
=
system_user
.
password
,
pkey
=
system_user
.
private_key
,
password
=
system_user
.
password
,
pkey
=
system_user
.
private_key
,
timeout
=
TIMEOUT
,
compress
=
True
,
auth_timeout
=
TIMEOUT
,
timeout
=
config
[
'SSH_TIMEOUT'
],
compress
=
True
,
auth_timeout
=
config
[
'SSH_TIMEOUT'
],
look_for_keys
=
False
,
sock
=
sock
look_for_keys
=
False
,
sock
=
sock
)
)
transport
=
ssh
.
get_transport
()
transport
.
set_keepalive
(
300
)
except
(
paramiko
.
AuthenticationException
,
except
(
paramiko
.
AuthenticationException
,
paramiko
.
BadAuthenticationType
,
paramiko
.
BadAuthenticationType
,
SSHException
)
as
e
:
SSHException
)
as
e
:
...
@@ -111,7 +115,7 @@ class SSHConnection:
...
@@ -111,7 +115,7 @@ class SSHConnection:
username
=
gateway
.
username
,
username
=
gateway
.
username
,
password
=
gateway
.
password
,
password
=
gateway
.
password
,
pkey
=
gateway
.
private_key_obj
,
pkey
=
gateway
.
private_key_obj
,
timeout
=
TIMEOUT
)
timeout
=
config
[
'SSH_TIMEOUT'
]
)
except
(
paramiko
.
AuthenticationException
,
except
(
paramiko
.
AuthenticationException
,
paramiko
.
BadAuthenticationType
,
paramiko
.
BadAuthenticationType
,
SSHException
):
SSHException
):
...
@@ -142,7 +146,7 @@ class SSHConnection:
...
@@ -142,7 +146,7 @@ class SSHConnection:
proxy_command
.
insert
(
0
,
"sshpass -p {}"
.
format
(
gateway
.
password
))
proxy_command
.
insert
(
0
,
"sshpass -p {}"
.
format
(
gateway
.
password
))
if
gateway
.
private_key
:
if
gateway
.
private_key
:
gateway
.
set_key_dir
(
os
.
path
.
join
(
self
.
app
.
root_path
,
'keys'
))
gateway
.
set_key_dir
(
os
.
path
.
join
(
config
[
'ROOT_PATH'
]
,
'keys'
))
proxy_command
.
append
(
"-i {}"
.
format
(
gateway
.
private_key_file
))
proxy_command
.
append
(
"-i {}"
.
format
(
gateway
.
private_key_file
))
proxy_command
=
' '
.
join
(
proxy_command
)
proxy_command
=
' '
.
join
(
proxy_command
)
...
...
coco/ctx.py
View file @
8a60c4eb
...
@@ -4,8 +4,11 @@
...
@@ -4,8 +4,11 @@
from
werkzeug.local
import
LocalProxy
from
werkzeug.local
import
LocalProxy
from
functools
import
partial
from
functools
import
partial
from
.config
import
config
from
jms.service
import
AppService
stack
=
{}
stack
=
{}
__db_sessions
=
[]
def
_find
(
name
):
def
_find
(
name
):
...
@@ -15,8 +18,6 @@ def _find(name):
...
@@ -15,8 +18,6 @@ def _find(name):
raise
ValueError
(
"Not found in stack: {}"
.
format
(
name
))
raise
ValueError
(
"Not found in stack: {}"
.
format
(
name
))
current_app
=
LocalProxy
(
partial
(
_find
,
'app'
))
app_service
=
AppService
(
config
)
app_service
=
LocalProxy
(
partial
(
_find
,
'service'
))
current_app
=
LocalProxy
(
partial
(
_find
,
'current_app'
))
# app_service = LocalProxy(partial(_find, 'app_service'))
# current_app = []
# current_service = []
coco/httpd.py
View file @
8a60c4eb
...
@@ -2,15 +2,15 @@
...
@@ -2,15 +2,15 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
import
os
import
os
import
socket
import
uuid
import
uuid
from
flask_socketio
import
SocketIO
,
Namespace
,
join_room
from
flask_socketio
import
SocketIO
,
Namespace
,
join_room
from
flask
import
Flask
,
request
,
current_app
,
redirect
from
flask
import
Flask
,
request
from
.models
import
Request
,
Client
,
WSProxy
from
.models
import
Connection
,
WSProxy
from
.proxy
import
ProxyServer
from
.proxy
import
ProxyServer
from
.utils
import
get_logger
from
.utils
import
get_logger
from
.ctx
import
current_app
,
app_service
from
.ctx
import
app_service
from
.config
import
config
BASE_DIR
=
os
.
path
.
dirname
(
os
.
path
.
dirname
(
__file__
))
BASE_DIR
=
os
.
path
.
dirname
(
os
.
path
.
dirname
(
__file__
))
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
...
@@ -26,16 +26,14 @@ class BaseNamespace(Namespace):
...
@@ -26,16 +26,14 @@ class BaseNamespace(Namespace):
def
get_current_user
(
self
):
def
get_current_user
(
self
):
session_id
=
request
.
cookies
.
get
(
'sessionid'
,
''
)
session_id
=
request
.
cookies
.
get
(
'sessionid'
,
''
)
csrf_token
=
request
.
cookies
.
get
(
'csrftoken'
,
''
)
csrf_token
=
request
.
cookies
.
get
(
'csrftoken'
,
''
)
token
=
request
.
headers
.
get
(
"Authorization"
)
user
=
None
user
=
None
if
session_id
and
csrf_token
:
if
session_id
and
csrf_token
:
user
=
app_service
.
check_user_cookie
(
session_id
,
csrf_token
)
user
=
app_service
.
check_user_cookie
(
session_id
,
csrf_token
)
if
token
:
msg
=
"Get current user: session_id<{}> => {}"
.
format
(
user
=
app_service
.
check_user_with_token
(
token
)
session_id
,
user
msg
=
"Get current user: session_id<{}> token<{}> => {}"
.
format
(
session_id
,
token
,
user
)
)
logger
.
debug
(
msg
)
logger
.
debug
(
msg
)
request
.
current_user
=
user
return
user
return
user
...
@@ -46,77 +44,54 @@ class ProxyNamespace(BaseNamespace):
...
@@ -46,77 +44,54 @@ class ProxyNamespace(BaseNamespace):
:param kwargs:
:param kwargs:
self.connections = {
self.connections = {
"request_sid": {
"request_sid": connection,
"room_id": {
"id": room_id,
"proxy": None,
"client": None,
"forwarder": None,
"request": None,
"cols": 80,
"rows": 24
},
...
},
...
...
}
}
"""
"""
super
()
.
__init__
(
*
args
,
**
kwargs
)
super
()
.
__init__
(
*
args
,
**
kwargs
)
self
.
connections
=
dict
()
self
.
win_size
=
None
self
.
win_size
=
None
def
new_connection
(
self
):
def
new_connection
(
self
):
self
.
connections
[
request
.
sid
]
=
dict
()
def
new_room
(
self
,
current_user
,
cols
=
80
,
rows
=
24
):
room_id
=
str
(
uuid
.
uuid4
())
req
=
self
.
make_coco_request
(
current_user
,
cols
=
cols
,
rows
=
rows
)
room
=
{
"id"
:
room_id
,
"proxy"
:
None
,
"client"
:
None
,
"forwarder"
:
None
,
"request"
:
req
,
"cols"
:
cols
,
"rows"
:
rows
}
self
.
connections
[
request
.
sid
][
room_id
]
=
room
return
room
@staticmethod
def
make_coco_request
(
user
,
cols
=
80
,
rows
=
24
):
x_forwarded_for
=
request
.
headers
.
get
(
"X-Forwarded-For"
,
''
)
.
split
(
','
)
x_forwarded_for
=
request
.
headers
.
get
(
"X-Forwarded-For"
,
''
)
.
split
(
','
)
if
x_forwarded_for
and
x_forwarded_for
[
0
]:
if
x_forwarded_for
and
x_forwarded_for
[
0
]:
remote_ip
=
x_forwarded_for
[
0
]
remote_ip
=
x_forwarded_for
[
0
]
else
:
else
:
remote_ip
=
request
.
remote_addr
remote_ip
=
request
.
remote_addr
connection
=
Connection
.
new_connection
(
req
=
Request
((
remote_ip
,
0
))
addr
=
(
remote_ip
,
0
),
cid
=
request
.
sid
,
sock
=
self
req
.
user
=
user
)
req
.
meta
=
{
"width"
:
cols
,
"height"
:
rows
}
connection
.
user
=
request
.
current_user
return
req
connection
.
login_from
=
'WT'
def
on_connect
(
self
):
def
on_connect
(
self
):
logger
.
debug
(
"On connect event trigger"
)
logger
.
debug
(
"On connect event trigger"
)
self
.
get_current_user
()
super
()
.
on_connect
()
super
()
.
on_connect
()
self
.
new_connection
()
self
.
new_connection
()
def
on_host
(
self
,
message
):
def
on_host
(
self
,
message
):
# 此处获取主机的信息
# 此处获取主机的信息
logger
.
debug
(
"On host event trigger"
)
logger
.
debug
(
"On host event trigger"
)
current_user
=
self
.
get_current_user
()
self
.
connect_host
(
message
)
self
.
connect_host
(
current_user
,
message
)
def
connect_host
(
self
,
current_user
,
message
):
def
connect_host
(
self
,
message
):
asset_id
=
message
.
get
(
'uuid'
,
None
)
asset_id
=
message
.
get
(
'uuid'
,
None
)
system_user_id
=
message
.
get
(
'userid'
,
None
)
system_user_id
=
message
.
get
(
'userid'
,
None
)
secret
=
message
.
get
(
'secret'
,
None
)
secret
=
message
.
get
(
'secret'
,
None
)
cols
,
rows
=
message
.
get
(
'size'
,
(
80
,
24
))
cols
,
rows
=
message
.
get
(
'size'
,
(
80
,
24
))
room
=
self
.
new_room
(
current_user
,
cols
=
cols
,
rows
=
rows
)
self
.
emit
(
'room'
,
{
'room'
:
room
[
"id"
],
'secret'
:
secret
})
connection
=
Connection
.
get_connection
(
request
.
sid
)
join_room
(
room
[
"id"
])
client_id
=
str
(
uuid
.
uuid4
())
client
=
connection
.
new_client
(
client_id
)
client
.
request
.
kind
=
'session'
client
.
request
.
type
=
'pty'
client
.
request
.
meta
.
update
({
'pty'
:
b
'xterm'
,
'width'
:
cols
,
'height'
:
rows
,
})
ws_proxy
=
WSProxy
(
self
,
client_id
)
client
.
chan
=
ws_proxy
self
.
emit
(
'room'
,
{
'room'
:
client_id
,
'secret'
:
secret
})
join_room
(
client_id
)
if
not
asset_id
or
not
system_user_id
:
if
not
asset_id
or
not
system_user_id
:
return
return
...
@@ -125,16 +100,12 @@ class ProxyNamespace(BaseNamespace):
...
@@ -125,16 +100,12 @@ class ProxyNamespace(BaseNamespace):
if
not
asset
or
not
system_user
:
if
not
asset
or
not
system_user
:
return
return
forwarder
=
ProxyServer
(
client
,
asset
,
system_user
)
child
,
parent
=
socket
.
socketpair
()
def
proxy
():
client
=
Client
(
parent
,
room
[
"request"
])
forwarder
.
proxy
()
forwarder
=
ProxyServer
(
client
,
login_from
=
'WT'
)
self
.
logout
(
client_id
,
connection
)
room
[
"client"
]
=
client
self
.
socketio
.
start_background_task
(
proxy
)
room
[
"forwarder"
]
=
forwarder
room
[
"proxy"
]
=
WSProxy
(
self
,
child
,
room
[
"id"
])
self
.
socketio
.
start_background_task
(
forwarder
.
proxy
,
asset
,
system_user
)
def
on_data
(
self
,
message
):
def
on_data
(
self
,
message
):
"""
"""
...
@@ -142,80 +113,98 @@ class ProxyNamespace(BaseNamespace):
...
@@ -142,80 +113,98 @@ class ProxyNamespace(BaseNamespace):
:param message: {"data": "xxx", "room": "xxx"}
:param message: {"data": "xxx", "room": "xxx"}
:return:
:return:
"""
"""
room
_id
=
message
.
get
(
'room'
)
client
_id
=
message
.
get
(
'room'
)
room
=
self
.
connections
.
get
(
request
.
sid
,
{})
.
get
(
room_
id
)
connection
=
Connection
.
get_connection
(
request
.
s
id
)
if
not
room
:
if
not
connection
:
return
return
room
[
"proxy"
]
.
send
({
"data"
:
message
[
'data'
]})
client
=
connection
.
clients
.
get
(
client_id
)
if
not
client
:
def
on_token
(
self
,
message
):
return
# 此处获取token含有的主机的信息
client
.
chan
.
write
(
message
.
get
(
"data"
,
""
))
logger
.
debug
(
"On token trigger"
)
token
=
message
.
get
(
'token'
,
None
)
secret
=
message
.
get
(
"secret"
,
None
)
win_size
=
message
.
get
(
'size'
,
(
80
,
24
))
room
=
self
.
new_room
(
None
)
self
.
emit
(
'room'
,
{
'room'
:
room
[
"id"
],
'secret'
:
secret
})
join_room
(
room
[
'id'
])
if
not
token
or
not
secret
:
def
check_token
(
self
,
token
,
secret
,
client_id
):
if
not
token
or
secret
:
msg
=
"Token or secret is None: {} {}"
.
format
(
token
,
secret
)
msg
=
"Token or secret is None: {} {}"
.
format
(
token
,
secret
)
logger
.
error
(
msg
)
logger
.
error
(
msg
)
self
.
emit
(
'data'
,
{
'data'
:
msg
,
'room'
:
room
[
'id'
]},
room
=
room
[
'id'
]
)
self
.
emit
(
'data'
,
{
'data'
:
msg
,
'room'
:
client_id
},
room
=
client_id
)
self
.
emit
(
'disconnect'
)
self
.
emit
(
'disconnect'
)
return
return
False
,
None
info
=
app_service
.
get_token_asset
(
token
)
info
=
app_service
.
get_token_asset
(
token
)
logger
.
debug
(
info
)
logger
.
debug
(
info
)
if
not
info
:
if
not
info
:
msg
=
"Token info is none, maybe token expired"
msg
=
"Token info is none, maybe token expired"
logger
.
error
(
msg
)
logger
.
error
(
msg
)
self
.
emit
(
'data'
,
{
'data'
:
msg
,
'room'
:
room
[
'id'
]},
room
=
room
[
'id'
]
)
self
.
emit
(
'data'
,
{
'data'
:
msg
,
'room'
:
client_id
},
room
=
client_id
)
self
.
emit
(
'disconnect'
)
self
.
emit
(
'disconnect'
)
return
None
return
False
,
None
return
True
,
info
def
on_token
(
self
,
message
):
# 此处获取token含有的主机的信息
logger
.
debug
(
"On token trigger"
)
token
=
message
.
get
(
'token'
,
None
)
secret
=
message
.
get
(
"secret"
,
None
)
win_size
=
message
.
get
(
'size'
,
(
80
,
24
))
client_id
=
str
(
uuid
.
uuid4
())
self
.
emit
(
'room'
,
{
'room'
:
client_id
,
'secret'
:
secret
})
join_room
(
client_id
)
valid
,
info
=
self
.
check_token
(
token
,
secret
,
client_id
)
if
not
valid
:
return
user_id
=
info
.
get
(
'user'
,
None
)
user_id
=
info
.
get
(
'user'
,
None
)
current_user
=
app_service
.
get_user_profile
(
user_id
)
request
.
current_user
=
app_service
.
get_user_profile
(
user_id
)
message
=
{
message
=
{
'secret'
:
secret
,
'secret'
:
secret
,
'uuid'
:
info
[
'asset'
],
'uuid'
:
info
[
'asset'
],
'userid'
:
info
[
'system_user'
],
'userid'
:
info
[
'system_user'
],
'size'
:
win_size
,
'size'
:
win_size
,
}
}
self
.
connect_host
(
current_user
,
message
)
self
.
connect_host
(
message
)
def
on_resize
(
self
,
message
):
def
on_resize
(
self
,
message
):
cols
,
rows
=
message
.
get
(
'cols'
,
None
),
message
.
get
(
'rows'
,
None
)
cols
,
rows
=
message
.
get
(
'cols'
,
None
),
message
.
get
(
'rows'
,
None
)
logger
.
debug
(
"On resize event trigger: {}*{}"
.
format
(
cols
,
rows
))
logger
.
debug
(
"On resize event trigger: {}*{}"
.
format
(
cols
,
rows
))
rooms
=
self
.
connections
.
get
(
request
.
sid
,
{})
connection
=
Connection
.
get_connection
(
request
.
sid
)
if
not
connection
:
logger
.
error
(
"Not connection found"
)
return
logger
.
debug
(
"Start change win size: {}*{}"
.
format
(
cols
,
rows
))
logger
.
debug
(
"Start change win size: {}*{}"
.
format
(
cols
,
rows
))
for
room
in
room
s
.
values
():
for
client
in
connection
.
client
s
.
values
():
if
(
room
[
"cols"
],
room
[
"rows
"
])
==
(
cols
,
rows
):
if
(
client
.
request
.
meta
[
"width"
],
client
.
request
.
meta
[
"height
"
])
==
(
cols
,
rows
):
continue
continue
room
[
"request"
]
.
meta
.
update
({
client
.
request
.
meta
.
update
({
'width'
:
cols
,
'height'
:
rows
'width'
:
cols
,
'height'
:
rows
})
})
room
[
"request"
]
.
change_size_event
.
set
()
client
.
change_size_evt
.
set
()
room
.
update
({
"cols"
:
cols
,
"rows"
:
rows
})
def
on_disconnect
(
self
):
def
on_disconnect
(
self
):
logger
.
debug
(
"On disconnect event trigger"
)
logger
.
debug
(
"On disconnect event trigger"
)
rooms
=
{
k
:
v
for
k
,
v
in
self
.
connections
.
get
(
request
.
sid
,
{})
.
items
()}
connection
=
Connection
.
get_connection
(
request
.
sid
)
for
room_id
in
rooms
:
if
not
connection
:
return
clients_copy
=
list
(
connection
.
clients
.
keys
())
for
client_id
in
clients_copy
:
try
:
try
:
self
.
on_logout
(
room
_id
)
self
.
on_logout
(
client
_id
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
warn
(
e
)
logger
.
warn
(
e
)
del
self
.
connections
[
request
.
sid
]
Connection
.
remove_connection
(
connection
.
id
)
def
on_logout
(
self
,
room_id
):
def
logout
(
self
,
client_id
,
connection
):
room
=
self
.
connections
.
get
(
request
.
sid
,
{})
.
get
(
room_id
)
connection
.
remove_client
(
client_id
)
if
room
:
self
.
emit
(
'logout'
,
{
"room"
:
client_id
},
room
=
client_id
)
room
.
get
(
"proxy"
)
and
room
[
"proxy"
]
.
close
()
self
.
close_room
(
room_id
)
def
on_logout
(
self
,
client_id
):
del
self
.
connections
[
request
.
sid
][
room_id
]
logger
.
debug
(
"On logout event trigger: {}"
.
format
(
client_id
))
del
room
connection
=
Connection
.
get_connection
(
request
.
sid
)
if
not
connection
:
return
self
.
logout
(
client_id
,
connection
)
def
close
(
self
):
pass
def
on_ping
(
self
):
def
on_ping
(
self
):
self
.
emit
(
'pong'
)
self
.
emit
(
'pong'
)
...
@@ -238,7 +227,6 @@ class HttpServer:
...
@@ -238,7 +227,6 @@ class HttpServer:
)
)
def
__init__
(
self
):
def
__init__
(
self
):
config
=
{
k
:
v
for
k
,
v
in
current_app
.
config
.
items
()}
config
.
update
(
self
.
config
)
config
.
update
(
self
.
config
)
self
.
flask_app
=
Flask
(
__name__
,
template_folder
=
'dist'
)
self
.
flask_app
=
Flask
(
__name__
,
template_folder
=
'dist'
)
self
.
flask_app
.
config
.
update
(
config
)
self
.
flask_app
.
config
.
update
(
config
)
...
@@ -257,8 +245,9 @@ class HttpServer:
...
@@ -257,8 +245,9 @@ class HttpServer:
self
.
socket_io
.
on_error_default
(
self
.
on_error_default
)
self
.
socket_io
.
on_error_default
(
self
.
on_error_default
)
def
run
(
self
):
def
run
(
self
):
host
=
self
.
flask_app
.
config
[
"BIND_HOST"
]
# return
port
=
self
.
flask_app
.
config
[
"HTTPD_PORT"
]
host
=
config
[
"BIND_HOST"
]
port
=
config
[
"HTTPD_PORT"
]
print
(
'Starting websocket server at {}:{}'
.
format
(
host
,
port
))
print
(
'Starting websocket server at {}:{}'
.
format
(
host
,
port
))
self
.
socket_io
.
init_app
(
self
.
socket_io
.
init_app
(
self
.
flask_app
,
self
.
flask_app
,
...
...
coco/interactive.py
View file @
8a60c4eb
...
@@ -7,11 +7,12 @@ import threading
...
@@ -7,11 +7,12 @@ import threading
import
os
import
os
from
.
import
char
from
.
import
char
from
.config
import
config
from
.utils
import
wrap_with_line_feed
as
wr
,
wrap_with_title
as
title
,
\
from
.utils
import
wrap_with_line_feed
as
wr
,
wrap_with_title
as
title
,
\
wrap_with_warning
as
warning
,
is_obj_attr_has
,
is_obj_attr_eq
,
\
wrap_with_warning
as
warning
,
is_obj_attr_has
,
is_obj_attr_eq
,
\
sort_assets
,
ugettext
as
_
,
get_logger
,
net_input
,
format_with_zh
,
\
sort_assets
,
ugettext
as
_
,
get_logger
,
net_input
,
format_with_zh
,
\
item_max_length
,
size_of_str_with_zh
item_max_length
,
size_of_str_with_zh
,
switch_lang
from
.ctx
import
current_app
,
app_service
from
.ctx
import
app_service
from
.proxy
import
ProxyServer
from
.proxy
import
ProxyServer
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
...
@@ -22,8 +23,8 @@ class InteractiveServer:
...
@@ -22,8 +23,8 @@ class InteractiveServer:
def
__init__
(
self
,
client
):
def
__init__
(
self
,
client
):
self
.
client
=
client
self
.
client
=
client
self
.
request
=
client
.
request
self
.
assets
=
None
self
.
assets
=
None
self
.
closed
=
False
self
.
_search_result
=
None
self
.
_search_result
=
None
self
.
nodes
=
None
self
.
nodes
=
None
self
.
get_user_assets_async
()
self
.
get_user_assets_async
()
...
@@ -44,28 +45,39 @@ class InteractiveServer:
...
@@ -44,28 +45,39 @@ class InteractiveServer:
value
=
self
.
filter_system_users
(
value
)
value
=
self
.
filter_system_users
(
value
)
self
.
_search_result
=
value
self
.
_search_result
=
value
def
display_
banner
(
self
):
def
display_
logo
(
self
):
self
.
client
.
send
(
char
.
CLEAR_CHAR
)
logo_path
=
os
.
path
.
join
(
config
[
'ROOT_PATH'
],
"logo.txt"
)
logo_path
=
os
.
path
.
join
(
current_app
.
root_path
,
"logo.txt"
)
if
not
os
.
path
.
isfile
(
logo_path
):
if
os
.
path
.
isfile
(
logo_path
):
return
with
open
(
logo_path
,
'rb'
)
as
f
:
with
open
(
logo_path
,
'rb'
)
as
f
:
for
i
in
f
:
for
i
in
f
:
if
i
.
decode
(
'utf-8'
)
.
startswith
(
'#'
):
if
i
.
decode
(
'utf-8'
)
.
startswith
(
'#'
):
continue
continue
self
.
client
.
send
(
i
.
decode
(
'utf-8'
)
.
replace
(
'
\n
'
,
'
\r\n
'
))
self
.
client
.
send
(
i
.
decode
(
'utf-8'
)
.
replace
(
'
\n
'
,
'
\r\n
'
))
banner
=
_
(
"""
\n
{title} {user}, 欢迎使用Jumpserver开源跳板机系统 {end}
\r\n\r
def
display_banner
(
self
):
1) 输入 {green}ID{end} 直接登录 或 输入{green}部分 IP,主机名,备注{end} 进行搜索登录(如果唯一).
\r
self
.
client
.
send
(
char
.
CLEAR_CHAR
)
2) 输入 {green}/{end} + {green}IP, 主机名{end} or {green}备注 {end}搜索. 如: /ip
\r
self
.
display_logo
()
3) 输入 {green}p{end} 显示您有权限的主机.
\r
header
=
_
(
"
\n
{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system {end}{R}{R}"
)
4) 输入 {green}g{end} 显示您有权限的节点
\r
menus
=
[
5) 输入 {green}g{end} + {green}组ID{end} 显示节点下主机. 如: g1
\r
_
(
"{T}1) Enter {green}ID{end} directly login or enter {green}part IP, Hostname, Comment{end} to search login(if unique).{R}"
),
6) 输入 {green}h{end} 帮助.
\r
_
(
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} search, such as: /ip.{R}"
),
0) 输入 {green}q{end} 退出.
\r\n
"""
)
.
format
(
_
(
"{T}3) Enter {green}p{end} to display the host you have permission.{R}"
),
title
=
"
\033
[1;32m"
,
green
=
"
\033
[32m"
,
_
(
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
),
end
=
"
\033
[0m"
,
user
=
self
.
client
.
user
_
(
"{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under the node, such as g1.{R}"
),
)
_
(
"{T}6) Enter {green}s{end} Chinese-english switch.{R}"
),
self
.
client
.
send
(
banner
)
_
(
"{T}7) Enter {green}h{end} help.{R}"
),
_
(
"{T}0) Enter {green}q{end} exit.{R}"
)
]
self
.
client
.
send
(
header
.
format
(
title
=
"
\033
[1;32m"
,
user
=
self
.
client
.
user
,
end
=
"
\033
[0m"
,
T
=
'
\t
'
,
R
=
'
\r\n\r
'
))
for
menu
in
menus
:
self
.
client
.
send
(
menu
.
format
(
green
=
"
\033
[32m"
,
end
=
"
\033
[0m"
,
T
=
'
\t
'
,
R
=
'
\r\n\r
'
))
def
dispatch
(
self
,
opt
):
def
dispatch
(
self
,
opt
):
if
opt
is
None
:
if
opt
is
None
:
...
@@ -80,6 +92,9 @@ class InteractiveServer:
...
@@ -80,6 +92,9 @@ class InteractiveServer:
self
.
display_node_assets
(
int
(
opt
.
lstrip
(
"g"
)))
self
.
display_node_assets
(
int
(
opt
.
lstrip
(
"g"
)))
elif
opt
in
[
'q'
,
'Q'
,
'exit'
,
'quit'
]:
elif
opt
in
[
'q'
,
'Q'
,
'exit'
,
'quit'
]:
return
self
.
_sentinel
return
self
.
_sentinel
elif
opt
in
[
's'
,
'S'
]:
switch_lang
()
self
.
display_banner
()
elif
opt
in
[
'h'
,
'H'
]:
elif
opt
in
[
'h'
,
'H'
]:
self
.
display_banner
()
self
.
display_banner
()
else
:
else
:
...
@@ -124,33 +139,27 @@ class InteractiveServer:
...
@@ -124,33 +139,27 @@ class InteractiveServer:
self
.
get_user_nodes
()
self
.
get_user_nodes
()
if
len
(
self
.
nodes
)
==
0
:
if
len
(
self
.
nodes
)
==
0
:
self
.
client
.
send
(
warning
(
_
(
"
无
"
)))
self
.
client
.
send
(
warning
(
_
(
"
No
"
)))
return
return
id_length
=
max
(
len
(
str
(
len
(
self
.
nodes
))),
5
)
id_length
=
max
(
len
(
str
(
len
(
self
.
nodes
))),
5
)
name_length
=
item_max_length
(
self
.
nodes
,
15
,
key
=
lambda
x
:
x
.
name
)
name_length
=
item_max_length
(
self
.
nodes
,
15
,
key
=
lambda
x
:
x
.
name
)
amount_length
=
item_max_length
(
self
.
nodes
,
10
,
amount_length
=
item_max_length
(
self
.
nodes
,
10
,
key
=
lambda
x
:
x
.
assets_amount
)
key
=
lambda
x
:
x
.
assets_amount
)
size_list
=
[
id_length
,
name_length
,
amount_length
]
size_list
=
[
id_length
,
name_length
,
amount_length
]
fake_data
=
[
'ID'
,
_
(
"Name"
),
_
(
"Assets"
)]
fake_data
=
[
'ID'
,
_
(
"Name"
),
_
(
"Assets"
)]
header_without_comment
=
format_with_zh
(
size_list
,
*
fake_data
)
comment_length
=
max
(
self
.
request
.
meta
[
"width"
]
-
size_of_str_with_zh
(
header_without_comment
)
-
1
,
2
)
size_list
.
append
(
comment_length
)
fake_data
.
append
(
_
(
"Comment"
))
self
.
client
.
send
(
title
(
format_with_zh
(
size_list
,
*
fake_data
)))
self
.
client
.
send
(
wr
(
title
(
format_with_zh
(
size_list
,
*
fake_data
)
)))
for
index
,
group
in
enumerate
(
self
.
nodes
,
1
):
for
index
,
node
in
enumerate
(
self
.
nodes
,
1
):
data
=
[
index
,
group
.
name
,
group
.
assets_amount
,
group
.
comme
nt
]
data
=
[
index
,
node
.
name
,
node
.
assets_amou
nt
]
self
.
client
.
send
(
wr
(
format_with_zh
(
size_list
,
*
data
)))
self
.
client
.
send
(
wr
(
format_with_zh
(
size_list
,
*
data
)))
self
.
client
.
send
(
wr
(
_
(
"
总共
: {}"
)
.
format
(
len
(
self
.
nodes
)),
before
=
1
))
self
.
client
.
send
(
wr
(
_
(
"
Total
: {}"
)
.
format
(
len
(
self
.
nodes
)),
before
=
1
))
def
display_node_assets
(
self
,
_id
):
def
display_node_assets
(
self
,
_id
):
if
self
.
nodes
is
None
:
self
.
get_user_nodes
()
if
_id
>
len
(
self
.
nodes
)
or
_id
<=
0
:
if
_id
>
len
(
self
.
nodes
)
or
_id
<=
0
:
self
.
client
.
send
(
wr
(
warning
(
"没有匹配分组,请重新输入"
)))
msg
=
wr
(
warning
(
_
(
"There is no matched node, please re-enter"
)))
self
.
client
.
send
(
msg
)
self
.
display_nodes
()
self
.
display_nodes
()
return
return
...
@@ -158,7 +167,7 @@ class InteractiveServer:
...
@@ -158,7 +167,7 @@ class InteractiveServer:
self
.
display_search_result
()
self
.
display_search_result
()
def
display_search_result
(
self
):
def
display_search_result
(
self
):
sort_by
=
c
urrent_app
.
c
onfig
[
"ASSET_LIST_SORT_BY"
]
sort_by
=
config
[
"ASSET_LIST_SORT_BY"
]
self
.
search_result
=
sort_assets
(
self
.
search_result
,
sort_by
)
self
.
search_result
=
sort_assets
(
self
.
search_result
,
sort_by
)
fake_data
=
[
_
(
"ID"
),
_
(
"Hostname"
),
_
(
"IP"
),
_
(
"LoginAs"
)]
fake_data
=
[
_
(
"ID"
),
_
(
"Hostname"
),
_
(
"IP"
),
_
(
"LoginAs"
)]
id_length
=
max
(
len
(
str
(
len
(
self
.
search_result
))),
4
)
id_length
=
max
(
len
(
str
(
len
(
self
.
search_result
))),
4
)
...
@@ -169,7 +178,7 @@ class InteractiveServer:
...
@@ -169,7 +178,7 @@ class InteractiveServer:
size_list
=
[
id_length
,
hostname_length
,
16
,
sysuser_length
]
size_list
=
[
id_length
,
hostname_length
,
16
,
sysuser_length
]
header_without_comment
=
format_with_zh
(
size_list
,
*
fake_data
)
header_without_comment
=
format_with_zh
(
size_list
,
*
fake_data
)
comment_length
=
max
(
comment_length
=
max
(
self
.
request
.
meta
[
"width"
]
-
self
.
client
.
request
.
meta
[
"width"
]
-
size_of_str_with_zh
(
header_without_comment
)
-
1
,
size_of_str_with_zh
(
header_without_comment
)
-
1
,
2
2
)
)
...
@@ -182,7 +191,7 @@ class InteractiveServer:
...
@@ -182,7 +191,7 @@ class InteractiveServer:
asset
.
system_users_name_list
,
asset
.
comment
asset
.
system_users_name_list
,
asset
.
comment
]
]
self
.
client
.
send
(
wr
(
format_with_zh
(
size_list
,
*
data
)))
self
.
client
.
send
(
wr
(
format_with_zh
(
size_list
,
*
data
)))
self
.
client
.
send
(
wr
(
_
(
"
总共: {} 匹配
: {}"
)
.
format
(
self
.
client
.
send
(
wr
(
_
(
"
Total: {} Match
: {}"
)
.
format
(
len
(
self
.
assets
),
len
(
self
.
search_result
)),
before
=
1
)
len
(
self
.
assets
),
len
(
self
.
search_result
)),
before
=
1
)
)
)
...
@@ -225,7 +234,7 @@ class InteractiveServer:
...
@@ -225,7 +234,7 @@ class InteractiveServer:
return
None
return
None
while
True
:
while
True
:
self
.
client
.
send
(
wr
(
_
(
"
选择一个登录
: "
),
after
=
1
))
self
.
client
.
send
(
wr
(
_
(
"
Select a login:
: "
),
after
=
1
))
self
.
display_system_users
(
system_users
)
self
.
display_system_users
(
system_users
)
opt
=
net_input
(
self
.
client
,
prompt
=
"ID> "
)
opt
=
net_input
(
self
.
client
,
prompt
=
"ID> "
)
if
opt
.
isdigit
()
and
len
(
system_users
)
>
int
(
opt
):
if
opt
.
isdigit
()
and
len
(
system_users
)
>
int
(
opt
):
...
@@ -248,7 +257,8 @@ class InteractiveServer:
...
@@ -248,7 +257,8 @@ class InteractiveServer:
self
.
search_result
=
None
self
.
search_result
=
None
if
asset
.
platform
==
"Windows"
:
if
asset
.
platform
==
"Windows"
:
self
.
client
.
send
(
warning
(
self
.
client
.
send
(
warning
(
_
(
"终端不支持登录windows, 请使用web terminal访问"
))
_
(
"Terminal does not support login Windows, "
"please use web terminal to access"
))
)
)
return
return
self
.
proxy
(
asset
)
self
.
proxy
(
asset
)
...
@@ -258,20 +268,21 @@ class InteractiveServer:
...
@@ -258,20 +268,21 @@ class InteractiveServer:
def
proxy
(
self
,
asset
):
def
proxy
(
self
,
asset
):
system_user
=
self
.
choose_system_user
(
asset
.
system_users_granted
)
system_user
=
self
.
choose_system_user
(
asset
.
system_users_granted
)
if
system_user
is
None
:
if
system_user
is
None
:
self
.
client
.
send
(
_
(
"
没有系统用户
"
))
self
.
client
.
send
(
_
(
"
No system user
"
))
return
return
forwarder
=
ProxyServer
(
self
.
client
,
login_from
=
'ST'
)
forwarder
=
ProxyServer
(
self
.
client
,
asset
,
system_user
)
forwarder
.
proxy
(
asset
,
system_user
)
forwarder
.
proxy
()
def
interact
(
self
):
def
interact
(
self
):
self
.
display_banner
()
self
.
display_banner
()
while
True
:
while
not
self
.
closed
:
try
:
try
:
opt
=
net_input
(
self
.
client
,
prompt
=
'Opt> '
,
before
=
1
)
opt
=
net_input
(
self
.
client
,
prompt
=
'Opt> '
,
before
=
1
)
rv
=
self
.
dispatch
(
opt
)
rv
=
self
.
dispatch
(
opt
)
if
rv
is
self
.
_sentinel
:
if
rv
is
self
.
_sentinel
:
break
break
except
socket
.
error
:
except
socket
.
error
as
e
:
logger
.
debug
(
"Socket error: {}"
.
format
(
e
))
break
break
self
.
close
()
self
.
close
()
...
@@ -281,7 +292,9 @@ class InteractiveServer:
...
@@ -281,7 +292,9 @@ class InteractiveServer:
thread
.
start
()
thread
.
start
()
def
close
(
self
):
def
close
(
self
):
current_app
.
remove_client
(
self
.
client
)
logger
.
debug
(
"Interactive server server close: {}"
.
format
(
self
))
self
.
closed
=
True
# current_app.remove_client(self.client)
# def __del__(self):
# def __del__(self):
# print("GC: Interactive class been gc")
# print("GC: Interactive class been gc")
coco/interface.py
View file @
8a60c4eb
...
@@ -4,9 +4,11 @@
...
@@ -4,9 +4,11 @@
import
paramiko
import
paramiko
import
threading
import
threading
from
collections
import
Iterable
from
.utils
import
get_logger
from
.utils
import
get_logger
from
.ctx
import
current_app
,
app_service
from
.config
import
config
from
.ctx
import
app_service
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
...
@@ -19,12 +21,13 @@ class SSHInterface(paramiko.ServerInterface):
...
@@ -19,12 +21,13 @@ class SSHInterface(paramiko.ServerInterface):
https://github.com/paramiko/paramiko/blob/master/demos/demo_server.py
https://github.com/paramiko/paramiko/blob/master/demos/demo_server.py
"""
"""
def
__init__
(
self
,
request
):
def
__init__
(
self
,
connection
):
self
.
request
=
request
self
.
connection
=
connection
self
.
event
=
threading
.
Event
()
self
.
event
=
threading
.
Event
()
self
.
auth_valid
=
False
self
.
auth_valid
=
False
self
.
otp_auth
=
False
self
.
otp_auth
=
False
self
.
info
=
None
self
.
info
=
None
self
.
user
=
None
def
check_auth_interactive
(
self
,
username
,
submethods
):
def
check_auth_interactive
(
self
,
username
,
submethods
):
logger
.
info
(
"Check auth interactive:
%
s
%
s"
%
(
username
,
submethods
))
logger
.
info
(
"Check auth interactive:
%
s
%
s"
%
(
username
,
submethods
))
...
@@ -58,9 +61,9 @@ class SSHInterface(paramiko.ServerInterface):
...
@@ -58,9 +61,9 @@ class SSHInterface(paramiko.ServerInterface):
supported
=
[]
supported
=
[]
if
self
.
otp_auth
:
if
self
.
otp_auth
:
return
'keyboard-interactive'
return
'keyboard-interactive'
if
c
urrent_app
.
c
onfig
[
"PASSWORD_AUTH"
]:
if
config
[
"PASSWORD_AUTH"
]:
supported
.
append
(
"password"
)
supported
.
append
(
"password"
)
if
c
urrent_app
.
c
onfig
[
"PUBLIC_KEY_AUTH"
]:
if
config
[
"PUBLIC_KEY_AUTH"
]:
supported
.
append
(
"publickey"
)
supported
.
append
(
"publickey"
)
return
","
.
join
(
supported
)
return
","
.
join
(
supported
)
...
@@ -69,6 +72,7 @@ class SSHInterface(paramiko.ServerInterface):
...
@@ -69,6 +72,7 @@ class SSHInterface(paramiko.ServerInterface):
def
check_auth_password
(
self
,
username
,
password
):
def
check_auth_password
(
self
,
username
,
password
):
user
=
self
.
validate_auth
(
username
,
password
=
password
)
user
=
self
.
validate_auth
(
username
,
password
=
password
)
if
not
user
:
if
not
user
:
logger
.
warning
(
"Password and public key auth <
%
s> failed, reject it"
%
username
)
logger
.
warning
(
"Password and public key auth <
%
s> failed, reject it"
%
username
)
return
paramiko
.
AUTH_FAILED
return
paramiko
.
AUTH_FAILED
...
@@ -81,6 +85,7 @@ class SSHInterface(paramiko.ServerInterface):
...
@@ -81,6 +85,7 @@ class SSHInterface(paramiko.ServerInterface):
def
check_auth_publickey
(
self
,
username
,
key
):
def
check_auth_publickey
(
self
,
username
,
key
):
key
=
key
.
get_base64
()
key
=
key
.
get_base64
()
user
=
self
.
validate_auth
(
username
,
public_key
=
key
)
user
=
self
.
validate_auth
(
username
,
public_key
=
key
)
if
not
user
:
if
not
user
:
logger
.
debug
(
"Public key auth <
%
s> failed, try to password"
%
username
)
logger
.
debug
(
"Public key auth <
%
s> failed, try to password"
%
username
)
return
paramiko
.
AUTH_FAILED
return
paramiko
.
AUTH_FAILED
...
@@ -90,113 +95,150 @@ class SSHInterface(paramiko.ServerInterface):
...
@@ -90,113 +95,150 @@ class SSHInterface(paramiko.ServerInterface):
return
paramiko
.
AUTH_PARTIALLY_SUCCESSFUL
return
paramiko
.
AUTH_PARTIALLY_SUCCESSFUL
return
paramiko
.
AUTH_SUCCESSFUL
return
paramiko
.
AUTH_SUCCESSFUL
@staticmethod
def
check_block_ssh_user
(
username
):
block_ssh_user
=
config
[
'BLOCK_SSH_USER'
]
if
not
block_ssh_user
or
not
isinstance
(
block_ssh_user
,
Iterable
):
return
False
if
username
in
block_ssh_user
:
return
True
else
:
return
False
@staticmethod
def
check_allow_ssh_user
(
username
):
allow_ssh_user
=
config
[
"ALLOW_SSH_USER"
]
if
not
allow_ssh_user
or
not
isinstance
(
allow_ssh_user
,
Iterable
):
return
True
if
username
in
allow_ssh_user
:
return
True
else
:
return
False
def
validate_auth
(
self
,
username
,
password
=
""
,
public_key
=
""
):
def
validate_auth
(
self
,
username
,
password
=
""
,
public_key
=
""
):
if
self
.
check_block_ssh_user
(
username
)
or
\
not
self
.
check_allow_ssh_user
(
username
):
logger
.
warn
(
"User in black list or not allowed: {}"
.
format
(
username
))
return
None
info
=
app_service
.
authenticate
(
info
=
app_service
.
authenticate
(
username
,
password
=
password
,
public_key
=
public_key
,
username
,
password
=
password
,
public_key
=
public_key
,
remote_addr
=
self
.
request
.
remote_ip
remote_addr
=
self
.
connection
.
addr
[
0
]
)
)
user
=
info
.
get
(
'user'
,
None
)
user
=
info
.
get
(
'user'
,
None
)
if
user
:
if
user
:
self
.
request
.
user
=
user
self
.
connection
.
user
=
user
self
.
info
=
info
self
.
info
=
info
seed
=
info
.
get
(
'seed'
,
None
)
seed
=
info
.
get
(
'seed'
,
None
)
token
=
info
.
get
(
'token'
,
None
)
token
=
info
.
get
(
'token'
,
None
)
if
seed
and
not
token
:
if
seed
and
not
token
:
self
.
connection
.
otp_auth
=
True
self
.
otp_auth
=
True
self
.
otp_auth
=
True
return
user
return
user
def
check_channel_direct_tcpip_request
(
self
,
chanid
,
origin
,
destination
):
def
check_channel_direct_tcpip_request
(
self
,
chan
_
id
,
origin
,
destination
):
logger
.
debug
(
"Check channel direct tcpip request:
%
d
%
s
%
s"
%
logger
.
debug
(
"Check channel direct tcpip request:
%
d
%
s
%
s"
%
(
chanid
,
origin
,
destination
))
(
chan_id
,
origin
,
destination
))
self
.
request
.
type
.
append
(
'direct-tcpip'
)
client
=
self
.
connection
.
new_client
(
chan_id
)
self
.
request
.
meta
.
update
({
client
.
request
.
kind
=
'direct-tcpip'
'chanid'
:
chanid
,
'origin'
:
origin
,
client
.
request
.
type
=
'direct-tcpip'
'destination'
:
destination
,
client
.
request
.
meta
.
update
({
'origin'
:
origin
,
'destination'
:
destination
})
})
self
.
event
.
set
()
self
.
event
.
set
()
return
0
return
0
def
check_port_forward_request
(
self
,
address
,
port
):
logger
.
info
(
"Check channel port forward request:
%
s
%
s"
%
(
address
,
port
)
)
self
.
event
.
set
()
return
False
def
check_channel_request
(
self
,
kind
,
chan_id
):
logger
.
info
(
"Check channel request:
%
s
%
d"
%
(
kind
,
chan_id
))
client
=
self
.
connection
.
new_client
(
chan_id
)
client
.
request
.
kind
=
kind
return
paramiko
.
OPEN_SUCCEEDED
def
check_channel_env_request
(
self
,
channel
,
name
,
value
):
def
check_channel_env_request
(
self
,
channel
,
name
,
value
):
logger
.
debug
(
"Check channel env request:
%
s,
%
s,
%
s"
%
logger
.
debug
(
"Check channel env request:
%
s,
%
s,
%
s"
%
(
channel
,
name
,
value
))
(
channel
.
get_id
(),
name
,
value
))
self
.
request
.
type
.
append
(
'env'
)
client
=
self
.
connection
.
get_client
(
channel
)
client
.
request
.
meta
[
'env'
][
name
]
=
value
return
False
return
False
def
check_channel_exec_request
(
self
,
channel
,
command
):
def
check_channel_exec_request
(
self
,
channel
,
command
):
logger
.
debug
(
"Check channel exec request: `
%
s`"
%
command
)
logger
.
debug
(
"Check channel exec request: `
%
s`"
%
command
)
self
.
request
.
type
.
append
(
'exec'
)
client
=
self
.
connection
.
get_client
(
channel
)
self
.
request
.
meta
.
update
({
'channel'
:
channel
.
get_id
(),
'command'
:
command
})
client
.
request
.
type
=
'exec'
client
.
request
.
meta
.
update
({
'command'
:
command
})
self
.
event
.
set
()
self
.
event
.
set
()
return
False
return
False
def
check_channel_forward_agent_request
(
self
,
channel
):
def
check_channel_forward_agent_request
(
self
,
channel
):
logger
.
debug
(
"Check channel forward agent request:
%
s"
%
channel
)
logger
.
debug
(
"Check channel forward agent request:
%
s"
%
channel
)
self
.
request
.
type
.
append
(
"forward-agent"
)
client
=
self
.
connection
.
get_client
(
channel
)
self
.
request
.
meta
.
update
({
'channel'
:
channel
.
get_id
()})
client
.
request
.
meta
[
'forward-agent'
]
=
True
self
.
event
.
set
()
self
.
event
.
set
()
return
Fals
e
return
Tru
e
def
check_channel_pty_request
(
def
check_channel_pty_request
(
self
,
channel
,
term
,
width
,
height
,
self
,
channel
,
term
,
width
,
height
,
pixelwidth
,
pixelheight
,
modes
):
pixelwidth
,
pixelheight
,
modes
):
logger
.
info
(
"Check channel pty request:
%
s
%
s
%
s
%
s
%
s"
%
logger
.
info
(
"Check channel pty request:
%
s
%
s
%
s
%
s
%
s"
%
(
term
,
width
,
height
,
pixelwidth
,
pixelheight
))
(
term
,
width
,
height
,
pixelwidth
,
pixelheight
))
self
.
request
.
type
.
append
(
'pty'
)
client
=
self
.
connection
.
get_client
(
channel
)
self
.
request
.
meta
.
update
({
client
.
request
.
type
=
'pty'
'channel'
:
channel
,
'term'
:
term
,
'width'
:
width
,
client
.
request
.
meta
.
update
({
'term'
:
term
,
'width'
:
width
,
'height'
:
height
,
'pixelwidth'
:
pixelwidth
,
'height'
:
height
,
'pixelwidth'
:
pixelwidth
,
'pixelheight'
:
pixelheight
,
'pixelheight'
:
pixelheight
,
})
})
self
.
event
.
set
()
self
.
event
.
set
()
return
True
return
True
def
check_channel_request
(
self
,
kind
,
chanid
):
logger
.
info
(
"Check channel request:
%
s
%
d"
%
(
kind
,
chanid
))
return
paramiko
.
OPEN_SUCCEEDED
def
check_channel_shell_request
(
self
,
channel
):
def
check_channel_shell_request
(
self
,
channel
):
logger
.
info
(
"Check channel shell request:
%
s"
%
channel
.
get_id
())
logger
.
info
(
"Check channel shell request:
%
s"
%
channel
.
get_id
())
self
.
event
.
set
()
client
=
self
.
connection
.
get_client
(
channel
)
client
.
request
.
meta
[
'shell'
]
=
True
return
True
return
True
def
check_channel_subsystem_request
(
self
,
channel
,
name
):
def
check_channel_subsystem_request
(
self
,
channel
,
name
):
logger
.
info
(
"Check channel subsystem request:
%
s
%
s"
%
(
channel
,
name
))
logger
.
info
(
"Check channel subsystem request:
%
s"
%
name
)
self
.
request
.
type
.
append
(
'subsystem'
)
client
=
self
.
connection
.
get_client
(
channel
)
self
.
request
.
meta
.
update
({
'channel'
:
channel
.
get_id
(),
'name'
:
name
})
client
.
request
.
type
=
'subsystem'
client
.
request
.
meta
[
'subsystem'
]
=
name
self
.
event
.
set
()
self
.
event
.
set
()
return
super
()
.
check_channel_subsystem_request
(
channel
,
name
)
return
super
()
.
check_channel_subsystem_request
(
channel
,
name
)
def
check_channel_window_change_request
(
self
,
channel
,
width
,
height
,
def
check_channel_window_change_request
(
self
,
channel
,
width
,
height
,
pixelwidth
,
pixelheight
):
pixelwidth
,
pixelheight
):
self
.
request
.
meta
.
update
({
client
=
self
.
connection
.
get_client
(
channel
)
client
.
request
.
meta
.
update
({
'width'
:
width
,
'width'
:
width
,
'height'
:
height
,
'height'
:
height
,
'pixelwidth'
:
pixelwidth
,
'pixelwidth'
:
pixelwidth
,
'pixelheight'
:
pixelheight
,
'pixelheight'
:
pixelheight
,
})
})
self
.
request
.
change_size_even
t
.
set
()
client
.
change_size_ev
t
.
set
()
return
True
return
True
def
check_channel_x11_request
(
self
,
channel
,
single_connection
,
def
check_channel_x11_request
(
self
,
channel
,
single_connection
,
auth_protocol
,
auth_cookie
,
screen_number
):
auth_protocol
,
auth_cookie
,
screen_number
):
logger
.
info
(
"Check channel x11 request
%
s
%
s
%
s
%
s
%
s
"
%
logger
.
info
(
"Check channel x11 request
%
s
%
s
%
s
%
s"
%
(
channel
,
single_connection
,
auth_protocol
,
(
single_connection
,
auth_protocol
,
auth_cookie
,
screen_number
))
auth_cookie
,
screen_number
))
self
.
request
.
type
.
append
(
'x11'
)
client
=
self
.
connection
.
get_client
(
channel
)
self
.
request
.
meta
.
update
({
# client.request_x11_event.set()
'channel'
:
channel
.
get_id
(),
'single_connection'
:
single_connection
,
client
.
request
.
meta
.
update
({
'auth_protocol'
:
auth_protocol
,
'auth_cookie'
:
auth_cookie
,
'single_connection'
:
single_connection
,
'auth_protocol'
:
auth_protocol
,
'auth_cookie'
:
auth_cookie
,
'screen_number'
:
screen_number
,
'screen_number'
:
screen_number
,
})
})
self
.
event
.
set
()
return
False
def
check_port_forward_request
(
self
,
address
,
port
):
logger
.
info
(
"Check channel port forward request:
%
s
%
s"
%
(
address
,
port
))
self
.
request
.
type
.
append
(
'port-forward'
)
self
.
request
.
meta
.
update
({
'address'
:
address
,
'port'
:
port
})
self
.
event
.
set
()
return
False
return
False
def
get_banner
(
self
):
def
get_banner
(
self
):
...
...
coco/logger.py
View file @
8a60c4eb
...
@@ -40,9 +40,12 @@ def create_logger(app):
...
@@ -40,9 +40,12 @@ def create_logger(app):
},
},
'file'
:
{
'file'
:
{
'level'
:
'DEBUG'
,
'level'
:
'DEBUG'
,
'class'
:
'logging.FileHandler'
,
'class'
:
'logging.
handlers.TimedRotating
FileHandler'
,
'formatter'
:
'main'
,
'formatter'
:
'main'
,
'filename'
:
log_path
,
'filename'
:
log_path
,
'when'
:
"D"
,
'interval'
:
1
,
"backupCount"
:
7
},
},
},
},
loggers
=
{
loggers
=
{
...
...
coco/models.py
View file @
8a60c4eb
#!/usr/bin/env python3
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
import
threading
import
datetime
import
weakref
import
weakref
import
time
import
uuid
import
socket
from
.struct
import
SizedList
,
SelectEvent
from
.
import
char
from
.
import
char
from
.
import
utils
from
.
import
utils
...
@@ -12,34 +12,85 @@ BUF_SIZE = 4096
...
@@ -12,34 +12,85 @@ BUF_SIZE = 4096
logger
=
utils
.
get_logger
(
__file__
)
logger
=
utils
.
get_logger
(
__file__
)
class
Request
:
class
Connection
:
def
__init__
(
self
,
addr
):
connections
=
{}
self
.
type
=
[]
clients_num
=
0
self
.
meta
=
{
"width"
:
80
,
"height"
:
24
}
self
.
user
=
None
def
__init__
(
self
,
cid
=
None
,
sock
=
None
,
addr
=
None
):
if
not
cid
:
cid
=
str
(
uuid
.
uuid4
())
self
.
id
=
cid
self
.
sock
=
sock
self
.
addr
=
addr
self
.
addr
=
addr
self
.
remote_ip
=
self
.
addr
[
0
]
self
.
user
=
None
self
.
change_size_event
=
threading
.
Event
()
self
.
otp_auth
=
False
self
.
date_start
=
datetime
.
datetime
.
now
()
self
.
login_from
=
'ST'
self
.
clients
=
{}
def
__str__
(
self
):
return
'<{} from {}>'
.
format
(
self
.
user
,
self
.
addr
)
def
new_client
(
self
,
tid
):
client
=
Client
(
tid
=
tid
,
user
=
self
.
user
,
addr
=
self
.
addr
,
login_from
=
self
.
login_from
)
client
.
connection_id
=
self
.
id
self
.
clients
[
tid
]
=
client
self
.
__class__
.
clients_num
+=
1
logger
.
info
(
"New client {} join, total {} now"
.
format
(
client
,
self
.
__class__
.
clients_num
))
return
client
def
get_client
(
self
,
tid
):
if
hasattr
(
tid
,
'get_id'
):
tid
=
tid
.
get_id
()
client
=
self
.
clients
.
get
(
tid
)
return
client
def
remove_client
(
self
,
tid
):
client
=
self
.
get_client
(
tid
)
if
not
client
:
return
client
.
close
()
self
.
__class__
.
clients_num
-=
1
del
self
.
clients
[
tid
]
logger
.
info
(
"Client {} leave, total {} now"
.
format
(
client
,
self
.
__class__
.
clients_num
))
def
close
(
self
):
clients_copy
=
[
k
for
k
in
self
.
clients
]
for
tid
in
clients_copy
:
self
.
remove_client
(
tid
)
self
.
sock
.
close
()
# def __del__(self):
@classmethod
# print("GC: Request object gc")
def
new_connection
(
cls
,
addr
,
sock
=
None
,
cid
=
None
):
if
not
cid
:
cid
=
str
(
uuid
.
uuid4
())
connection
=
cls
(
cid
=
cid
,
sock
=
sock
,
addr
=
addr
)
cls
.
connections
[
cid
]
=
connection
return
connection
@classmethod
def
remove_connection
(
cls
,
cid
):
connection
=
cls
.
get_connection
(
cid
)
connection
.
close
()
del
cls
.
connections
[
cid
]
class
SizedList
(
list
):
@classmethod
def
__init__
(
self
,
maxsize
=
0
):
def
get_connection
(
cls
,
cid
):
self
.
maxsize
=
maxsize
return
cls
.
connections
.
get
(
cid
)
self
.
size
=
0
super
()
.
__init__
()
def
append
(
self
,
b
):
if
self
.
maxsize
==
0
or
self
.
size
<
self
.
maxsize
:
super
()
.
append
(
b
)
self
.
size
+=
len
(
b
)
def
clean
(
self
):
class
Request
:
self
.
size
=
0
def
__init__
(
self
):
del
self
[:]
self
.
type
=
None
self
.
x11
=
None
self
.
kind
=
None
self
.
meta
=
{
'env'
:
{}}
class
Client
:
class
Client
:
...
@@ -51,11 +102,17 @@ class Client:
...
@@ -51,11 +102,17 @@ class Client:
```
```
"""
"""
def
__init__
(
self
,
chan
,
request
):
def
__init__
(
self
,
tid
=
None
,
user
=
None
,
addr
=
None
,
login_from
=
None
):
self
.
chan
=
chan
if
tid
is
None
:
self
.
request
=
request
tid
=
str
(
uuid
.
uuid4
())
self
.
user
=
request
.
user
self
.
id
=
tid
self
.
addr
=
request
.
addr
self
.
user
=
user
self
.
addr
=
addr
self
.
chan
=
None
self
.
request
=
Request
()
self
.
connection_id
=
None
self
.
login_from
=
login_from
self
.
change_size_evt
=
SelectEvent
()
def
fileno
(
self
):
def
fileno
(
self
):
return
self
.
chan
.
fileno
()
return
self
.
chan
.
fileno
()
...
@@ -82,9 +139,6 @@ class Client:
...
@@ -82,9 +139,6 @@ class Client:
def
__str__
(
self
):
def
__str__
(
self
):
return
"<
%
s from
%
s:
%
s>"
%
(
self
.
user
,
self
.
addr
[
0
],
self
.
addr
[
1
])
return
"<
%
s from
%
s:
%
s>"
%
(
self
.
user
,
self
.
addr
[
0
],
self
.
addr
[
1
])
# def __del__(self):
# print("GC: Client object has been gc")
class
BaseServer
:
class
BaseServer
:
"""
"""
...
@@ -94,9 +148,7 @@ class BaseServer:
...
@@ -94,9 +148,7 @@ class BaseServer:
"""
"""
def
__init__
(
self
):
def
__init__
(
self
):
self
.
send_bytes
=
0
self
.
chan
=
None
self
.
recv_bytes
=
0
self
.
stop_evt
=
threading
.
Event
()
self
.
input_data
=
SizedList
(
maxsize
=
1024
)
self
.
input_data
=
SizedList
(
maxsize
=
1024
)
self
.
output_data
=
SizedList
(
maxsize
=
1024
)
self
.
output_data
=
SizedList
(
maxsize
=
1024
)
...
@@ -107,6 +159,13 @@ class BaseServer:
...
@@ -107,6 +159,13 @@ class BaseServer:
self
.
_input
=
""
self
.
_input
=
""
self
.
_output
=
""
self
.
_output
=
""
self
.
_session_ref
=
None
self
.
_session_ref
=
None
self
.
_zmodem_recv_start_mark
=
b
'rz waiting to receive.**
\x18
B0100'
self
.
_zmodem_send_start_mark
=
b
'**
\x18
B00000000000000'
self
.
_zmodem_cancel_mark
=
b
'
\x18\x18\x18\x18\x18
'
self
.
_zmodem_end_mark
=
b
'**
\x18
B0800000000022d'
self
.
_zmodem_state_send
=
'send'
self
.
_zmodem_state_recv
=
'recv'
self
.
_zmodem_state
=
''
def
set_session
(
self
,
session
):
def
set_session
(
self
,
session
):
self
.
_session_ref
=
weakref
.
ref
(
session
)
self
.
_session_ref
=
weakref
.
ref
(
session
)
...
@@ -118,16 +177,17 @@ class BaseServer:
...
@@ -118,16 +177,17 @@ class BaseServer:
else
:
else
:
return
None
return
None
def
parse
(
self
,
b
):
def
initial_filter
(
self
):
if
isinstance
(
b
,
str
):
b
=
b
.
encode
(
"utf-8"
)
if
not
self
.
_input_initial
:
if
not
self
.
_input_initial
:
self
.
_input_initial
=
True
self
.
_input_initial
=
True
if
self
.
_have_enter_char
(
b
):
def
parse_cmd_filter
(
self
,
data
):
# 输入了回车键, 开始计算输入的内容
if
self
.
_have_enter_char
(
data
):
self
.
_in_input_state
=
False
self
.
_in_input_state
=
False
self
.
_input
=
self
.
_parse_input
()
self
.
_input
=
self
.
_parse_input
()
else
:
return
data
# 用户输入了内容,但是如果没在输入状态,也就是用户刚开始输入了,结算上次输出内容
if
not
self
.
_in_input_state
:
if
not
self
.
_in_input_state
:
self
.
_output
=
self
.
_parse_output
()
self
.
_output
=
self
.
_parse_output
()
logger
.
debug
(
"
\n
{}
\n
Input: {}
\n
Output: {}
\n
{}"
.
format
(
logger
.
debug
(
"
\n
{}
\n
Input: {}
\n
Output: {}
\n
{}"
.
format
(
...
@@ -140,6 +200,56 @@ class BaseServer:
...
@@ -140,6 +200,56 @@ class BaseServer:
self
.
input_data
.
clean
()
self
.
input_data
.
clean
()
self
.
output_data
.
clean
()
self
.
output_data
.
clean
()
self
.
_in_input_state
=
True
self
.
_in_input_state
=
True
return
data
def
send
(
self
,
data
):
self
.
initial_filter
()
self
.
parse_cmd_filter
(
data
)
return
self
.
chan
.
send
(
data
)
def
replay_filter
(
self
,
data
):
if
not
self
.
_zmodem_state
:
self
.
session
.
put_replay
(
data
)
def
input_output_filter
(
self
,
data
):
if
not
self
.
_input_initial
:
return
if
self
.
_zmodem_state
:
return
if
self
.
_in_input_state
:
self
.
input_data
.
append
(
data
)
else
:
self
.
output_data
.
append
(
data
)
def
zmodem_state_filter
(
self
,
data
):
if
not
self
.
_zmodem_state
:
if
data
[:
50
]
.
find
(
self
.
_zmodem_recv_start_mark
)
!=
-
1
:
logger
.
debug
(
"Zmodem state => recv"
)
self
.
_zmodem_state
=
self
.
_zmodem_state_recv
elif
data
[:
24
]
.
find
(
self
.
_zmodem_send_start_mark
)
!=
-
1
:
logger
.
debug
(
"Zmodem state => send"
)
self
.
_zmodem_state
=
self
.
_zmodem_state_send
if
self
.
_zmodem_state
:
if
data
[:
24
]
.
find
(
self
.
_zmodem_end_mark
)
!=
-
1
:
logger
.
debug
(
"Zmodem state => end"
)
self
.
_zmodem_state
=
''
elif
data
[:
24
]
.
find
(
self
.
_zmodem_cancel_mark
)
!=
-
1
:
logger
.
debug
(
"Zmodem state => cancel"
)
self
.
_zmodem_state
=
''
def
zmodem_cancel_filter
(
self
):
if
self
.
_zmodem_state
:
pass
# self.chan.send(self._zmodem_cancel_mark)
# self.chan.send("Zmodem disabled")
def
recv
(
self
,
size
):
data
=
self
.
chan
.
recv
(
size
)
self
.
zmodem_state_filter
(
data
)
self
.
zmodem_cancel_filter
()
self
.
replay_filter
(
data
)
self
.
input_output_filter
(
data
)
return
data
@staticmethod
@staticmethod
def
_have_enter_char
(
s
):
def
_have_enter_char
(
s
):
...
@@ -155,107 +265,38 @@ class BaseServer:
...
@@ -155,107 +265,38 @@ class BaseServer:
return
parser
.
parse_output
(
self
.
output_data
)
return
parser
.
parse_output
(
self
.
output_data
)
def
_parse_input
(
self
):
def
_parse_input
(
self
):
if
not
self
.
input_data
or
self
.
input_data
[
0
]
==
char
.
RZ_PROTOCOL_CHAR
:
if
not
self
.
input_data
:
return
return
parser
=
utils
.
TtyIOParser
()
parser
=
utils
.
TtyIOParser
()
return
parser
.
parse_input
(
self
.
input_data
)
return
parser
.
parse_input
(
self
.
input_data
)
class
TelnetServer
(
BaseServer
):
"""
Telnet server
"""
def
__init__
(
self
,
sock
,
asset
,
system_user
):
super
(
TelnetServer
,
self
)
.
__init__
()
self
.
sock
=
sock
self
.
asset
=
asset
self
.
system_user
=
system_user
def
fileno
(
self
):
def
fileno
(
self
):
return
self
.
sock
.
fileno
()
return
self
.
chan
.
fileno
()
def
send
(
self
,
b
):
self
.
parse
(
b
)
return
self
.
sock
.
send
(
b
)
def
recv
(
self
,
size
):
data
=
self
.
sock
.
recv
(
size
)
self
.
session
.
put_replay
(
data
)
if
self
.
_input_initial
:
if
self
.
_in_input_state
:
self
.
input_data
.
append
(
data
)
else
:
self
.
output_data
.
append
(
data
)
return
data
def
close
(
self
):
def
close
(
self
):
logger
.
info
(
"Closed server {}"
.
format
(
self
))
logger
.
info
(
"Closed server {}"
.
format
(
self
))
self
.
parse
(
b
''
)
self
.
input_output_filter
(
b
''
)
self
.
stop_evt
.
set
()
self
.
chan
.
close
()
self
.
sock
.
close
()
def
__getattr__
(
self
,
item
):
def
__getattr__
(
self
,
item
):
return
getattr
(
self
.
sock
,
item
)
return
getattr
(
self
.
chan
,
item
)
def
__str__
(
self
):
def
__str__
(
self
):
return
"<To: {}>"
.
format
(
str
(
self
.
asset
))
return
"<To: {}>"
.
format
(
str
(
self
.
asset
))
class
Server
(
BaseServer
):
class
Telnet
Server
(
BaseServer
):
"""
"""
SSH Server
Telnet server
Server object like client, a wrapper object, a connection to the asset,
Because we don't want to using python dynamic feature, such asset
have the chan and system_user attr.
"""
"""
def
__init__
(
self
,
sock
,
asset
,
system_user
):
# Todo: Server name is not very suitable
super
(
TelnetServer
,
self
)
.
__init__
()
def
__init__
(
self
,
chan
,
sock
,
asset
,
system_user
):
self
.
chan
=
sock
super
(
Server
,
self
)
.
__init__
()
self
.
chan
=
chan
self
.
sock
=
sock
self
.
asset
=
asset
self
.
asset
=
asset
self
.
system_user
=
system_user
self
.
system_user
=
system_user
def
fileno
(
self
):
return
self
.
chan
.
fileno
()
def
send
(
self
,
b
):
self
.
parse
(
b
)
return
self
.
chan
.
send
(
b
)
def
recv
(
self
,
size
):
data
=
self
.
chan
.
recv
(
size
)
self
.
session
.
put_replay
(
data
)
if
self
.
_input_initial
:
if
self
.
_in_input_state
:
self
.
input_data
.
append
(
data
)
else
:
self
.
output_data
.
append
(
data
)
return
data
def
close
(
self
):
logger
.
info
(
"Closed server {}"
.
format
(
self
))
self
.
parse
(
b
''
)
self
.
stop_evt
.
set
()
self
.
chan
.
close
()
self
.
chan
.
transport
.
close
()
if
self
.
sock
:
self
.
sock
.
transport
.
close
()
def
__getattr__
(
self
,
item
):
return
getattr
(
self
.
chan
,
item
)
def
__str__
(
self
):
return
"<To: {}>"
.
format
(
str
(
self
.
asset
))
# def __del__(self):
# print("GC: Server object has been gc")
'''
class
Server
(
BaseServer
):
class Server:
"""
"""
SSH Server
SSH Server
Server object like client, a wrapper object, a connection to the asset,
Server object like client, a wrapper object, a connection to the asset,
...
@@ -265,185 +306,39 @@ class Server:
...
@@ -265,185 +306,39 @@ class Server:
# Todo: Server name is not very suitable
# Todo: Server name is not very suitable
def
__init__
(
self
,
chan
,
sock
,
asset
,
system_user
):
def
__init__
(
self
,
chan
,
sock
,
asset
,
system_user
):
super
(
Server
,
self
)
.
__init__
()
self
.
chan
=
chan
self
.
chan
=
chan
self
.
sock
=
sock
self
.
sock
=
sock
self
.
asset
=
asset
self
.
asset
=
asset
self
.
system_user
=
system_user
self
.
system_user
=
system_user
self.send_bytes = 0
self.recv_bytes = 0
self.stop_evt = threading.Event()
self.input_data = SizedList(maxsize=1024)
self.output_data = SizedList(maxsize=1024)
self._in_input_state = True
self._input_initial = False
self._in_vim_state = False
self._input = ""
self._output = ""
self._session_ref = None
def fileno(self):
return self.chan.fileno()
def set_session(self, session):
self._session_ref = weakref.ref(session)
@property
def session(self):
if self._session_ref:
return self._session_ref()
else:
return None
def parse(self, b):
if isinstance(b, str):
b = b.encode("utf-8")
if not self._input_initial:
self._input_initial = True
if self._have_enter_char(b):
self._in_input_state = False
self._input = self._parse_input()
else:
if not self._in_input_state:
self._output = self._parse_output()
logger.debug("
\n
{}
\n
Input: {}
\n
Output: {}
\n
{}".format(
"#" * 30 + " Command " + "#" * 30,
self._input, self._output,
"#" * 30 + " End " + "#" * 30,
))
if self._input:
self.session.put_command(self._input, self._output)
self.input_data.clean()
self.output_data.clean()
self._in_input_state = True
def send(self, b):
self.parse(b)
return self.chan.send(b)
def recv(self, size):
data = self.chan.recv(size)
self.session.put_replay(data)
if self._input_initial:
if self._in_input_state:
self.input_data.append(data)
else:
self.output_data.append(data)
return data
def
close
(
self
):
def
close
(
self
):
logger.info("Closed server {}".format(self))
super
()
.
close
()
self.parse(b'')
self.stop_evt.set()
self.chan.close()
self
.
chan
.
transport
.
close
()
self
.
chan
.
transport
.
close
()
if
self
.
sock
:
if
self
.
sock
:
self
.
sock
.
transport
.
close
()
self
.
sock
.
transport
.
close
()
@staticmethod
def _have_enter_char(s):
for c in char.ENTER_CHAR:
if c in s:
return True
return False
def _parse_output(self):
if not self.output_data:
return ''
parser = utils.TtyIOParser()
return parser.parse_output(self.output_data)
def _parse_input(self):
if not self.input_data or self.input_data[0] == char.RZ_PROTOCOL_CHAR:
return
parser = utils.TtyIOParser()
return parser.parse_input(self.input_data)
def __getattr__(self, item):
return getattr(self.chan, item)
def __str__(self):
return "<To: {}>".format(str(self.asset))
# def __del__(self):
# print("GC: Server object has been gc")
'''
class
WSProxy
:
class
WSProxy
:
"""
def
__init__
(
self
,
ws
,
client_id
):
WSProxy is websocket proxy channel object.
Because tornado or flask websocket base event, if we want reuse func
with sshd, we need change it to socket, so we implement a proxy.
we should use socket pair implement it. usage:
```
child, parent = socket.socketpair()
# self must have write_message method, write message to ws
proxy = WSProxy(self, child)
client = Client(parent, user)
```
"""
def
__init__
(
self
,
ws
,
child
,
room_id
):
"""
:param ws: websocket instance or handler, have write_message method
:param child: sock child pair
"""
self
.
ws
=
ws
self
.
ws
=
ws
self
.
child
=
child
self
.
client_id
=
client_id
self
.
stop_event
=
threading
.
Event
()
self
.
sock
,
self
.
proxy
=
socket
.
socketpair
()
self
.
room_id
=
room_id
self
.
auto_forward
()
def
send
(
self
,
msg
):
def
send
(
self
,
data
):
"""
_data
=
{
'data'
:
data
.
decode
(
errors
=
"ignore"
),
'room'
:
self
.
client_id
},
If ws use proxy send data, then send the data to child sock, then
self
.
ws
.
emit
(
"data"
,
_data
,
room
=
self
.
client_id
)
the parent sock recv
:param msg: terminal write message {"data": "message"}
@property
:return:
def
closed
(
self
):
"""
return
self
.
sock
.
_closed
data
=
msg
[
"data"
]
if
isinstance
(
data
,
str
):
data
=
data
.
encode
(
'utf-8'
)
self
.
child
.
send
(
data
)
def
forward
(
self
):
def
write
(
self
,
data
):
while
not
self
.
stop_event
.
is_set
():
self
.
proxy
.
send
(
data
.
encode
())
try
:
data
=
self
.
child
.
recv
(
BUF_SIZE
)
except
(
OSError
,
EOFError
):
self
.
close
()
break
if
not
data
:
self
.
close
()
break
data
=
data
.
decode
(
errors
=
"ignore"
)
self
.
ws
.
emit
(
"data"
,
{
'data'
:
data
,
'room'
:
self
.
room_id
},
room
=
self
.
room_id
)
if
len
(
data
)
==
BUF_SIZE
:
time
.
sleep
(
0.1
)
def
auto_forward
(
self
):
thread
=
threading
.
Thread
(
target
=
self
.
forward
,
args
=
())
thread
.
daemon
=
True
thread
.
start
()
def
close
(
self
):
def
close
(
self
):
self
.
ws
.
emit
(
"logout"
,
{
"room"
:
self
.
room_id
},
room
=
self
.
room_id
)
self
.
proxy
.
close
()
self
.
stop_event
.
set
()
try
:
def
__getattr__
(
self
,
item
):
self
.
child
.
shutdown
(
1
)
return
getattr
(
self
.
sock
,
item
)
self
.
child
.
close
()
except
(
OSError
,
EOFError
):
pass
logger
.
debug
(
"Proxy {} closed"
.
format
(
self
))
coco/proxy.py
View file @
8a60c4eb
...
@@ -5,159 +5,136 @@
...
@@ -5,159 +5,136 @@
import
threading
import
threading
import
time
import
time
from
paramiko.ssh_exception
import
SSHException
from
.session
import
Session
from
.session
import
Session
from
.models
import
Server
,
TelnetServer
from
.models
import
Server
,
TelnetServer
from
.connection
import
SSHConnection
,
TelnetConnection
from
.connection
import
SSHConnection
,
TelnetConnection
from
.ctx
import
current_app
,
app_service
from
.ctx
import
app_service
from
.config
import
config
from
.utils
import
wrap_with_line_feed
as
wr
,
wrap_with_warning
as
warning
,
\
from
.utils
import
wrap_with_line_feed
as
wr
,
wrap_with_warning
as
warning
,
\
get_logger
,
net_input
get_logger
,
net_input
,
ugettext
as
_
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
TIMEOUT
=
10
BUF_SIZE
=
4096
BUF_SIZE
=
4096
MANUAL_LOGIN
=
'manual'
MANUAL_LOGIN
=
'manual'
AUTO_LOGIN
=
'auto'
AUTO_LOGIN
=
'auto'
class
ProxyServer
:
class
ProxyServer
:
def
__init__
(
self
,
client
,
login_from
):
def
__init__
(
self
,
client
,
asset
,
system_user
):
self
.
client
=
client
self
.
client
=
client
self
.
asset
=
asset
self
.
system_user
=
system_user
self
.
server
=
None
self
.
server
=
None
self
.
login_from
=
login_from
self
.
connecting
=
True
self
.
connecting
=
True
self
.
stop_event
=
threading
.
Event
()
def
get_system_user_auth
(
self
,
system_user
):
def
get_system_user_auth
_or_manual_set
(
self
):
"""
"""
获取系统用户的认证信息,密码或秘钥
获取系统用户的认证信息,密码或秘钥
:return: system user have full info
:return: system user have full info
"""
"""
password
,
private_key
=
\
password
,
private_key
=
\
app_service
.
get_system_user_auth_info
(
system_user
)
app_service
.
get_system_user_auth_info
(
self
.
system_user
)
if
system_user
.
login_mode
==
MANUAL_LOGIN
or
(
not
password
and
not
private_key
):
if
self
.
system_user
.
login_mode
==
MANUAL_LOGIN
\
prompt
=
"{}'s password: "
.
format
(
system_user
.
username
)
or
(
not
password
and
not
private_key
):
prompt
=
"{}'s password: "
.
format
(
self
.
system_user
.
username
)
password
=
net_input
(
self
.
client
,
prompt
=
prompt
,
sensitive
=
True
)
password
=
net_input
(
self
.
client
,
prompt
=
prompt
,
sensitive
=
True
)
system_user
.
password
=
password
s
elf
.
s
ystem_user
.
password
=
password
system_user
.
private_key
=
private_key
s
elf
.
s
ystem_user
.
private_key
=
private_key
def
proxy
(
self
,
asset
,
system_user
):
def
check_protocol
(
self
):
if
asset
.
protocol
!=
system_user
.
protocol
:
if
self
.
asset
.
protocol
!=
self
.
system_user
.
protocol
:
msg
=
'System user <{}> and asset <{}> protocol are inconsistent.'
.
format
(
msg
=
'System user <{}> and asset <{}> protocol are inconsistent.'
.
format
(
s
ystem_user
.
name
,
asset
.
hostname
s
elf
.
system_user
.
name
,
self
.
asset
.
hostname
)
)
self
.
client
.
send
(
warning
(
wr
(
msg
,
before
=
1
,
after
=
0
)))
self
.
client
.
send
(
warning
(
wr
(
msg
,
before
=
1
,
after
=
0
)))
return
False
return
True
def
manual_set_system_user_username_if_need
(
self
):
if
self
.
system_user
.
login_mode
==
MANUAL_LOGIN
and
\
not
self
.
system_user
.
username
:
username
=
net_input
(
self
.
client
,
prompt
=
'username: '
,
before
=
1
)
self
.
system_user
.
username
=
username
return
True
return
False
def
proxy
(
self
):
if
not
self
.
check_protocol
():
return
return
self
.
manual_set_system_user_username_if_need
()
if
system_user
.
login_mode
==
MANUAL_LOGIN
and
not
system_user
.
username
:
self
.
get_system_user_auth_or_manual_set
()
system_user_name
=
net_input
(
self
.
client
,
prompt
=
'username: '
,
before
=
1
)
self
.
server
=
self
.
get_server_conn
()
system_user
.
username
=
system_user_name
self
.
get_system_user_auth
(
system_user
)
self
.
send_connecting_message
(
asset
,
system_user
)
self
.
server
=
self
.
get_server_conn
(
asset
,
system_user
)
if
self
.
server
is
None
:
if
self
.
server
is
None
:
return
return
command_recorder
=
current_app
.
new_command_recorder
()
session
=
Session
.
new_session
(
self
.
client
,
self
.
server
)
replay_recorder
=
current_app
.
new_replay_recorder
()
session
=
Session
(
self
.
client
,
self
.
server
,
self
.
login_from
,
command_recorder
=
command_recorder
,
replay_recorder
=
replay_recorder
,
)
current_app
.
add_session
(
session
)
self
.
watch_win_size_change_async
()
session
.
bridge
()
session
.
bridge
()
self
.
stop_event
.
set
()
Session
.
remove_session
(
session
.
id
)
self
.
end_watch_win_size_change
()
self
.
server
.
close
()
current_app
.
remove_session
(
session
)
def
validate_permission
(
self
,
asset
,
system_user
):
def
validate_permission
(
self
):
"""
"""
验证用户是否有连接改资产的权限
验证用户是否有连接改资产的权限
:return: True or False
:return: True or False
"""
"""
return
app_service
.
validate_user_asset_permission
(
return
app_service
.
validate_user_asset_permission
(
self
.
client
.
user
.
id
,
asset
.
id
,
system_user
.
id
self
.
client
.
user
.
id
,
self
.
asset
.
id
,
self
.
system_user
.
id
)
)
def
get_server_conn
(
self
,
asset
,
system_user
):
def
get_server_conn
(
self
):
logger
.
info
(
"Connect to {}"
.
format
(
asset
.
hostname
))
logger
.
info
(
"Connect to {}"
.
format
(
self
.
asset
.
hostname
))
if
not
self
.
validate_permission
(
asset
,
system_user
):
self
.
send_connecting_message
()
self
.
client
.
send
(
warning
(
'No permission'
))
if
not
self
.
validate_permission
():
return
None
self
.
client
.
send
(
warning
(
_
(
'No permission'
)))
if
system_user
.
protocol
==
asset
.
protocol
==
'telnet'
:
server
=
None
server
=
self
.
get_telnet_server_conn
(
asset
,
system_user
)
elif
self
.
system_user
.
protocol
==
self
.
asset
.
protocol
==
'telnet'
:
elif
system_user
.
protocol
==
asset
.
protocol
==
'ssh'
:
server
=
self
.
get_telnet_server_conn
()
server
=
self
.
get_ssh_server_conn
(
asset
,
system_user
)
elif
self
.
system_user
.
protocol
==
self
.
asset
.
protocol
==
'ssh'
:
server
=
self
.
get_ssh_server_conn
()
else
:
else
:
server
=
None
server
=
None
self
.
connecting
=
False
self
.
client
.
send
(
b
'
\r\n
'
)
return
server
return
server
# Todo: Support telnet
def
get_telnet_server_conn
(
self
):
def
get_telnet_server_conn
(
self
,
asset
,
system_user
):
telnet
=
TelnetConnection
(
self
.
asset
,
self
.
system_user
,
self
.
client
)
telnet
=
TelnetConnection
(
asset
,
system_user
,
self
.
client
)
sock
,
msg
=
telnet
.
get_socket
()
sock
,
msg
=
telnet
.
get_socket
()
if
not
sock
:
if
not
sock
:
self
.
client
.
send
(
warning
(
wr
(
msg
,
before
=
1
,
after
=
0
)))
self
.
client
.
send
(
warning
(
wr
(
msg
,
before
=
1
,
after
=
0
)))
server
=
None
server
=
None
else
:
else
:
server
=
TelnetServer
(
sock
,
asset
,
system_user
)
server
=
TelnetServer
(
sock
,
self
.
asset
,
self
.
system_user
)
# self.client.send(b'\r\n')
self
.
connecting
=
False
return
server
return
server
def
get_ssh_server_conn
(
self
,
asset
,
system_user
):
def
get_ssh_server_conn
(
self
):
request
=
self
.
client
.
request
request
=
self
.
client
.
request
term
=
request
.
meta
.
get
(
'term'
,
'xterm'
)
term
=
request
.
meta
.
get
(
'term'
,
'xterm'
)
width
=
request
.
meta
.
get
(
'width'
,
80
)
width
=
request
.
meta
.
get
(
'width'
,
80
)
height
=
request
.
meta
.
get
(
'height'
,
24
)
height
=
request
.
meta
.
get
(
'height'
,
24
)
ssh
=
SSHConnection
()
ssh
=
SSHConnection
()
chan
,
sock
,
msg
=
ssh
.
get_channel
(
chan
,
sock
,
msg
=
ssh
.
get_channel
(
asset
,
system_user
,
term
=
term
,
width
=
width
,
height
=
height
self
.
asset
,
self
.
system_user
,
term
=
term
,
width
=
width
,
height
=
height
)
)
if
not
chan
:
if
not
chan
:
self
.
client
.
send
(
warning
(
wr
(
msg
,
before
=
1
,
after
=
0
)))
self
.
client
.
send
(
warning
(
wr
(
msg
,
before
=
1
,
after
=
0
)))
server
=
None
server
=
None
else
:
else
:
server
=
Server
(
chan
,
sock
,
asset
,
system_user
)
server
=
Server
(
chan
,
sock
,
self
.
asset
,
self
.
system_user
)
self
.
connecting
=
False
self
.
client
.
send
(
b
'
\r\n
'
)
return
server
return
server
def
watch_win_size_change
(
self
):
def
send_connecting_message
(
self
):
while
self
.
client
.
request
.
change_size_event
.
wait
():
if
self
.
stop_event
.
is_set
():
break
self
.
client
.
request
.
change_size_event
.
clear
()
width
=
self
.
client
.
request
.
meta
.
get
(
'width'
,
80
)
height
=
self
.
client
.
request
.
meta
.
get
(
'height'
,
24
)
logger
.
debug
(
"Change win size:
%
s -
%
s"
%
(
width
,
height
))
try
:
self
.
server
.
chan
.
resize_pty
(
width
=
width
,
height
=
height
)
except
SSHException
:
break
def
watch_win_size_change_async
(
self
):
if
not
isinstance
(
self
.
server
,
Server
):
return
thread
=
threading
.
Thread
(
target
=
self
.
watch_win_size_change
)
thread
.
daemon
=
True
thread
.
start
()
def
end_watch_win_size_change
(
self
):
self
.
client
.
request
.
change_size_event
.
set
()
def
send_connecting_message
(
self
,
asset
,
system_user
):
def
func
():
def
func
():
delay
=
0.0
delay
=
0.0
self
.
client
.
send
(
'Connecting to {}@{} {:.1f}'
.
format
(
self
.
client
.
send
(
_
(
'Connecting to {}@{} {:.1f}'
)
.
format
(
s
ystem_user
,
asset
,
delay
)
s
elf
.
system_user
,
self
.
asset
,
delay
)
)
)
while
self
.
connecting
and
delay
<
TIMEOUT
:
while
self
.
connecting
and
delay
<
config
[
'SSH_TIMEOUT'
]:
if
0
<=
delay
<
10
:
self
.
client
.
send
(
'
\x08\x08\x08
{:.1f}'
.
format
(
delay
)
.
encode
())
self
.
client
.
send
(
'
\x08\x08\x08
{:.1f}'
.
format
(
delay
)
.
encode
())
else
:
self
.
client
.
send
(
'
\x08\x08\x08\x08
{:.1f}'
.
format
(
delay
)
.
encode
())
time
.
sleep
(
0.1
)
time
.
sleep
(
0.1
)
delay
+=
0.1
delay
+=
0.1
thread
=
threading
.
Thread
(
target
=
func
)
thread
=
threading
.
Thread
(
target
=
func
)
...
...
coco/recorder.py
View file @
8a60c4eb
...
@@ -12,9 +12,10 @@ from copy import deepcopy
...
@@ -12,9 +12,10 @@ from copy import deepcopy
import
jms_storage
import
jms_storage
from
.config
import
config
from
.utils
import
get_logger
,
Singleton
from
.utils
import
get_logger
,
Singleton
from
.
alignmen
t
import
MemoryQueue
from
.
struc
t
import
MemoryQueue
from
.ctx
import
current_app
,
app_service
from
.ctx
import
app_service
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
BUF_SIZE
=
1024
BUF_SIZE
=
1024
...
@@ -48,7 +49,7 @@ class ReplayRecorder(metaclass=abc.ABCMeta):
...
@@ -48,7 +49,7 @@ class ReplayRecorder(metaclass=abc.ABCMeta):
def
session_start
(
self
,
session_id
):
def
session_start
(
self
,
session_id
):
self
.
time_start
=
time
.
time
()
self
.
time_start
=
time
.
time
()
filename
=
session_id
+
'.replay.gz'
filename
=
session_id
+
'.replay.gz'
self
.
file_path
=
os
.
path
.
join
(
c
urrent_app
.
c
onfig
[
'LOG_DIR'
],
filename
)
self
.
file_path
=
os
.
path
.
join
(
config
[
'LOG_DIR'
],
filename
)
self
.
file
=
gzip
.
open
(
self
.
file_path
,
'at'
)
self
.
file
=
gzip
.
open
(
self
.
file_path
,
'at'
)
self
.
file
.
write
(
'{'
)
self
.
file
.
write
(
'{'
)
...
@@ -58,9 +59,9 @@ class ReplayRecorder(metaclass=abc.ABCMeta):
...
@@ -58,9 +59,9 @@ class ReplayRecorder(metaclass=abc.ABCMeta):
self
.
upload_replay
(
session_id
)
self
.
upload_replay
(
session_id
)
def
get_storage
(
self
):
def
get_storage
(
self
):
conf
ig
=
deepcopy
(
current_app
.
config
[
"REPLAY_STORAGE"
])
conf
=
deepcopy
(
config
[
"REPLAY_STORAGE"
])
conf
ig
[
"SERVICE"
]
=
app_service
conf
[
"SERVICE"
]
=
app_service
self
.
storage
=
jms_storage
.
get_object_storage
(
conf
ig
)
self
.
storage
=
jms_storage
.
get_object_storage
(
conf
)
def
upload_replay
(
self
,
session_id
,
times
=
3
):
def
upload_replay
(
self
,
session_id
,
times
=
3
):
if
times
<
1
:
if
times
<
1
:
...
@@ -130,9 +131,9 @@ class CommandRecorder(metaclass=Singleton):
...
@@ -130,9 +131,9 @@ class CommandRecorder(metaclass=Singleton):
self
.
queue
.
put
(
data
)
self
.
queue
.
put
(
data
)
def
get_storage
(
self
):
def
get_storage
(
self
):
conf
ig
=
deepcopy
(
current_app
.
config
[
"COMMAND_STORAGE"
])
conf
=
deepcopy
(
config
[
"COMMAND_STORAGE"
])
conf
ig
[
'SERVICE'
]
=
app_service
conf
[
'SERVICE'
]
=
app_service
self
.
storage
=
jms_storage
.
get_log_storage
(
conf
ig
)
self
.
storage
=
jms_storage
.
get_log_storage
(
conf
)
def
push_to_server_async
(
self
):
def
push_to_server_async
(
self
):
def
func
():
def
func
():
...
@@ -153,9 +154,19 @@ class CommandRecorder(metaclass=Singleton):
...
@@ -153,9 +154,19 @@ class CommandRecorder(metaclass=Singleton):
thread
.
start
()
thread
.
start
()
def
session_start
(
self
,
session_id
):
def
session_start
(
self
,
session_id
):
print
(
"Session start: {}"
.
format
(
session_id
))
pass
pass
def
session_end
(
self
,
session_id
):
def
session_end
(
self
,
session_id
):
print
(
"Session end: {}"
.
format
(
session_id
))
pass
pass
def
get_command_recorder
():
return
CommandRecorder
()
def
get_replay_recorder
():
return
ReplayRecorder
()
def
get_recorder
():
return
get_command_recorder
(),
get_replay_recorder
()
\ No newline at end of file
coco/service.py
0 → 100644
View file @
8a60c4eb
# -*- coding: utf-8 -*-
#
from
.ctx
import
stack
def
init_app
(
app
):
stack
[
'current_app'
]
=
app
coco/session.py
View file @
8a60c4eb
#!/usr/bin/env python3
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
import
threading
import
uuid
import
uuid
import
datetime
import
datetime
import
selectors
import
selectors
import
time
import
time
from
.utils
import
get_logger
from
.utils
import
get_logger
,
wrap_with_warning
as
warn
,
\
wrap_with_line_feed
as
wr
,
ugettext
as
_
,
ignore_error
from
.ctx
import
app_service
from
.struct
import
SelectEvent
from
.recorder
import
get_recorder
BUF_SIZE
=
1024
BUF_SIZE
=
1024
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
class
Session
:
class
Session
:
def
__init__
(
self
,
client
,
server
,
login_from
,
command_recorder
=
None
,
replay_recorder
=
None
):
sessions
=
{}
def
__init__
(
self
,
client
,
server
):
self
.
id
=
str
(
uuid
.
uuid4
())
self
.
id
=
str
(
uuid
.
uuid4
())
self
.
client
=
client
# Master of the session, it's a client sock
self
.
client
=
client
# Master of the session, it's a client sock
self
.
server
=
server
# Server channel
self
.
server
=
server
# Server channel
self
.
login_from
=
login_from
# Login from
self
.
_watchers
=
[]
# Only watch session
self
.
_watchers
=
[]
# Only watch session
self
.
_sharers
=
[]
# Join to the session, read and write
self
.
_sharers
=
[]
# Join to the session, read and write
self
.
replaying
=
True
self
.
replaying
=
True
self
.
date_
created
=
datetime
.
datetime
.
utcnow
()
self
.
date_
start
=
datetime
.
datetime
.
utcnow
()
self
.
date_end
=
None
self
.
date_end
=
None
self
.
stop_evt
=
threading
.
Event
()
self
.
is_finished
=
False
self
.
closed
=
False
self
.
sel
=
selectors
.
DefaultSelector
()
self
.
sel
=
selectors
.
DefaultSelector
()
self
.
_command_recorder
=
command_recorder
self
.
_command_recorder
=
None
self
.
_replay_recorder
=
replay_recorder
self
.
_replay_recorder
=
None
self
.
stop_evt
=
SelectEvent
()
self
.
server
.
set_session
(
self
)
self
.
server
.
set_session
(
self
)
self
.
date_last_active
=
datetime
.
datetime
.
utcnow
()
self
.
date_last_active
=
datetime
.
datetime
.
utcnow
()
@classmethod
def
new_session
(
cls
,
client
,
server
):
session
=
cls
(
client
,
server
)
command_recorder
,
replay_recorder
=
get_recorder
()
session
.
set_command_recorder
(
command_recorder
)
session
.
set_replay_recorder
(
replay_recorder
)
cls
.
sessions
[
session
.
id
]
=
session
app_service
.
create_session
(
session
.
to_json
())
return
session
@classmethod
def
get_session
(
cls
,
sid
):
return
cls
.
sessions
.
get
(
sid
)
@classmethod
def
remove_session
(
cls
,
sid
):
session
=
cls
.
get_session
(
sid
)
if
session
:
session
.
close
()
app_service
.
finish_session
(
session
.
to_json
())
app_service
.
finish_replay
(
sid
)
del
cls
.
sessions
[
sid
]
def
add_watcher
(
self
,
watcher
,
silent
=
False
):
def
add_watcher
(
self
,
watcher
,
silent
=
False
):
"""
"""
Add a watcher, and will be transport server side msg to it.
Add a watcher, and will be transport server side msg to it.
...
@@ -64,6 +93,10 @@ class Session:
...
@@ -64,6 +93,10 @@ class Session:
self
.
sel
.
register
(
sharer
,
selectors
.
EVENT_READ
)
self
.
sel
.
register
(
sharer
,
selectors
.
EVENT_READ
)
self
.
_sharers
.
append
(
sharer
)
self
.
_sharers
.
append
(
sharer
)
@property
def
closed_unexpected
(
self
):
return
not
self
.
is_finished
and
(
self
.
client
.
closed
or
self
.
server
.
closed
)
def
remove_sharer
(
self
,
sharer
):
def
remove_sharer
(
self
,
sharer
):
logger
.
info
(
"Session
%
s remove sharer
%
s"
%
(
self
.
id
,
sharer
))
logger
.
info
(
"Session
%
s remove sharer
%
s"
%
(
self
.
id
,
sharer
))
sharer
.
send
(
"Leave session {} at {}"
sharer
.
send
(
"Leave session {} at {}"
...
@@ -79,8 +112,6 @@ class Session:
...
@@ -79,8 +112,6 @@ class Session:
self
.
_replay_recorder
=
recorder
self
.
_replay_recorder
=
recorder
def
put_command
(
self
,
_input
,
_output
):
def
put_command
(
self
,
_input
,
_output
):
if
not
_input
:
return
self
.
_command_recorder
.
record
({
self
.
_command_recorder
.
record
({
"session"
:
self
.
id
,
"session"
:
self
.
id
,
"org_id"
:
self
.
server
.
asset
.
org_id
,
"org_id"
:
self
.
server
.
asset
.
org_id
,
...
@@ -107,13 +138,14 @@ class Session:
...
@@ -107,13 +138,14 @@ class Session:
self
.
_replay_recorder
.
session_end
(
self
.
id
)
self
.
_replay_recorder
.
session_end
(
self
.
id
)
self
.
_command_recorder
.
session_end
(
self
.
id
)
self
.
_command_recorder
.
session_end
(
self
.
id
)
def
terminate
(
self
):
def
terminate
(
self
,
msg
=
None
):
msg
=
b
"Terminate by administrator
\r\n
"
if
not
msg
:
msg
=
_
(
"Terminated by administrator"
)
try
:
try
:
self
.
client
.
send
(
msg
)
self
.
client
.
send
(
wr
(
warn
(
msg
),
before
=
1
)
)
except
OSError
:
except
OSError
:
pass
pass
self
.
close
()
self
.
stop_evt
.
set
()
def
bridge
(
self
):
def
bridge
(
self
):
"""
"""
...
@@ -124,16 +156,17 @@ class Session:
...
@@ -124,16 +156,17 @@ class Session:
self
.
pre_bridge
()
self
.
pre_bridge
()
self
.
sel
.
register
(
self
.
client
,
selectors
.
EVENT_READ
)
self
.
sel
.
register
(
self
.
client
,
selectors
.
EVENT_READ
)
self
.
sel
.
register
(
self
.
server
,
selectors
.
EVENT_READ
)
self
.
sel
.
register
(
self
.
server
,
selectors
.
EVENT_READ
)
while
not
self
.
stop_evt
.
is_set
():
self
.
sel
.
register
(
self
.
stop_evt
,
selectors
.
EVENT_READ
)
events
=
self
.
sel
.
select
()
self
.
sel
.
register
(
self
.
client
.
change_size_evt
,
selectors
.
EVENT_READ
)
while
not
self
.
is_finished
:
events
=
self
.
sel
.
select
(
timeout
=
60
)
for
sock
in
[
key
.
fileobj
for
key
,
_
in
events
]:
for
sock
in
[
key
.
fileobj
for
key
,
_
in
events
]:
data
=
sock
.
recv
(
BUF_SIZE
)
data
=
sock
.
recv
(
BUF_SIZE
)
# self.put_replay(data)
if
sock
==
self
.
server
:
if
sock
==
self
.
server
:
if
len
(
data
)
==
0
:
if
len
(
data
)
==
0
:
msg
=
"Server close the connection"
msg
=
"Server close the connection"
logger
.
info
(
msg
)
logger
.
info
(
msg
)
self
.
close
()
self
.
is_finished
=
True
break
break
self
.
date_last_active
=
datetime
.
datetime
.
utcnow
()
self
.
date_last_active
=
datetime
.
datetime
.
utcnow
()
...
@@ -145,30 +178,32 @@ class Session:
...
@@ -145,30 +178,32 @@ class Session:
logger
.
info
(
msg
)
logger
.
info
(
msg
)
for
watcher
in
self
.
_watchers
+
self
.
_sharers
:
for
watcher
in
self
.
_watchers
+
self
.
_sharers
:
watcher
.
send
(
msg
.
encode
(
"utf-8"
))
watcher
.
send
(
msg
.
encode
(
"utf-8"
))
self
.
close
()
self
.
is_finished
=
True
break
break
self
.
server
.
send
(
data
)
self
.
server
.
send
(
data
)
elif
sock
in
self
.
_sharers
:
elif
sock
==
self
.
stop_evt
:
if
len
(
data
)
==
0
:
self
.
is_finished
=
True
logger
.
info
(
"Sharer {} leave the session {}"
.
format
(
sock
,
self
.
id
))
break
self
.
remove_sharer
(
sock
)
elif
sock
==
self
.
client
.
change_size_evt
:
self
.
server
.
send
(
data
)
self
.
resize_win_size
()
elif
sock
in
self
.
_watchers
:
if
len
(
data
)
==
0
:
self
.
_watchers
.
remove
(
sock
)
logger
.
info
(
"Watcher {} leave the session {}"
.
format
(
sock
,
self
.
id
))
logger
.
info
(
"Session stop event set: {}"
.
format
(
self
.
id
))
logger
.
info
(
"Session stop event set: {}"
.
format
(
self
.
id
))
def
set_size
(
self
,
width
,
height
):
def
resize_win_size
(
self
):
width
,
height
=
self
.
client
.
request
.
meta
[
'width'
],
\
self
.
client
.
request
.
meta
[
'height'
]
logger
.
debug
(
"Resize server chan size {}*{}"
.
format
(
width
,
height
))
logger
.
debug
(
"Resize server chan size {}*{}"
.
format
(
width
,
height
))
self
.
server
.
resize_pty
(
width
=
width
,
height
=
height
)
self
.
server
.
resize_pty
(
width
=
width
,
height
=
height
)
@ignore_error
def
close
(
self
):
def
close
(
self
):
if
self
.
closed
:
logger
.
info
(
"Session has been closed: {} "
.
format
(
self
.
id
))
return
logger
.
info
(
"Close the session: {} "
.
format
(
self
.
id
))
logger
.
info
(
"Close the session: {} "
.
format
(
self
.
id
))
self
.
stop_evt
.
set
()
self
.
is_finished
=
True
self
.
closed
=
True
self
.
post_bridge
()
self
.
post_bridge
()
self
.
date_end
=
datetime
.
datetime
.
utcnow
()
self
.
date_end
=
datetime
.
datetime
.
utcnow
()
self
.
server
.
close
()
def
to_json
(
self
):
def
to_json
(
self
):
return
{
return
{
...
@@ -177,11 +212,10 @@ class Session:
...
@@ -177,11 +212,10 @@ class Session:
"asset"
:
self
.
server
.
asset
.
hostname
,
"asset"
:
self
.
server
.
asset
.
hostname
,
"org_id"
:
self
.
server
.
asset
.
org_id
,
"org_id"
:
self
.
server
.
asset
.
org_id
,
"system_user"
:
self
.
server
.
system_user
.
username
,
"system_user"
:
self
.
server
.
system_user
.
username
,
"login_from"
:
self
.
login_from
,
"login_from"
:
self
.
client
.
login_from
,
"remote_addr"
:
self
.
client
.
addr
[
0
],
"remote_addr"
:
self
.
client
.
addr
[
0
],
"is_finished"
:
True
if
self
.
stop_evt
.
is_set
()
else
False
,
"is_finished"
:
self
.
is_finished
,
"date_last_active"
:
self
.
date_last_active
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
)
+
" +0000"
,
"date_start"
:
self
.
date_start
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
)
+
" +0000"
,
"date_start"
:
self
.
date_created
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
)
+
" +0000"
,
"date_end"
:
self
.
date_end
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
)
+
" +0000"
if
self
.
date_end
else
None
"date_end"
:
self
.
date_end
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
)
+
" +0000"
if
self
.
date_end
else
None
}
}
...
...
coco/sftp.py
View file @
8a60c4eb
...
@@ -2,9 +2,9 @@ import os
...
@@ -2,9 +2,9 @@ import os
import
tempfile
import
tempfile
import
paramiko
import
paramiko
import
time
import
time
from
.ctx
import
app_service
from
datetime
import
datetime
from
datetime
import
datetime
from
.ctx
import
app_service
from
.connection
import
SSHConnection
from
.connection
import
SSHConnection
...
@@ -53,7 +53,7 @@ class SFTPServer(paramiko.SFTPServerInterface):
...
@@ -53,7 +53,7 @@ class SFTPServer(paramiko.SFTPServerInterface):
def
get_perm_hosts
(
self
):
def
get_perm_hosts
(
self
):
hosts
=
{}
hosts
=
{}
assets
=
app_service
.
get_user_assets
(
assets
=
app_service
.
get_user_assets
(
self
.
server
.
request
.
user
self
.
server
.
connection
.
user
)
)
for
asset
in
assets
:
for
asset
in
assets
:
key
=
asset
.
hostname
key
=
asset
.
hostname
...
...
coco/sshd.py
View file @
8a60c4eb
...
@@ -5,14 +5,15 @@
...
@@ -5,14 +5,15 @@
import
os
import
os
import
socket
import
socket
import
threading
import
threading
import
paramiko
import
paramiko
from
.utils
import
ssh_key_gen
,
get_logger
from
.utils
import
ssh_key_gen
,
get_logger
from
.interface
import
SSHInterface
from
.interface
import
SSHInterface
from
.interactive
import
InteractiveServer
from
.interactive
import
InteractiveServer
from
.models
import
C
lient
,
Request
from
.models
import
C
onnection
from
.sftp
import
SFTPServer
from
.sftp
import
SFTPServer
from
.c
tx
import
current_app
from
.c
onfig
import
config
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
BACKLOG
=
5
BACKLOG
=
5
...
@@ -24,10 +25,11 @@ class SSHServer:
...
@@ -24,10 +25,11 @@ class SSHServer:
self
.
stop_evt
=
threading
.
Event
()
self
.
stop_evt
=
threading
.
Event
()
self
.
workers
=
[]
self
.
workers
=
[]
self
.
pipe
=
None
self
.
pipe
=
None
self
.
connections
=
[]
@property
@property
def
host_key
(
self
):
def
host_key
(
self
):
host_key_path
=
os
.
path
.
join
(
c
urrent_app
.
root_path
,
'keys'
,
'host_rsa_key'
)
host_key_path
=
os
.
path
.
join
(
c
onfig
[
'ROOT_PATH'
]
,
'keys'
,
'host_rsa_key'
)
if
not
os
.
path
.
isfile
(
host_key_path
):
if
not
os
.
path
.
isfile
(
host_key_path
):
self
.
gen_host_key
(
host_key_path
)
self
.
gen_host_key
(
host_key_path
)
return
paramiko
.
RSAKey
(
filename
=
host_key_path
)
return
paramiko
.
RSAKey
(
filename
=
host_key_path
)
...
@@ -39,24 +41,27 @@ class SSHServer:
...
@@ -39,24 +41,27 @@ class SSHServer:
f
.
write
(
ssh_key
)
f
.
write
(
ssh_key
)
def
run
(
self
):
def
run
(
self
):
host
=
c
urrent_app
.
c
onfig
[
"BIND_HOST"
]
host
=
config
[
"BIND_HOST"
]
port
=
c
urrent_app
.
c
onfig
[
"SSHD_PORT"
]
port
=
config
[
"SSHD_PORT"
]
print
(
'Starting ssh server at {}:{}'
.
format
(
host
,
port
))
print
(
'Starting ssh server at {}:{}'
.
format
(
host
,
port
))
sock
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
sock
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
sock
.
setsockopt
(
socket
.
SOL_SOCKET
,
socket
.
SO_REUSEADDR
,
1
)
sock
.
setsockopt
(
socket
.
SOL_SOCKET
,
socket
.
SO_REUSEADDR
,
True
)
sock
.
bind
((
host
,
port
))
sock
.
bind
((
host
,
port
))
sock
.
listen
(
BACKLOG
)
sock
.
listen
(
BACKLOG
)
while
not
self
.
stop_evt
.
is_set
():
while
not
self
.
stop_evt
.
is_set
():
try
:
try
:
client
,
addr
=
sock
.
accept
()
client
,
addr
=
sock
.
accept
()
logger
.
info
(
"Get ssh request from {}: {}"
.
format
(
*
addr
))
t
=
threading
.
Thread
(
target
=
self
.
handle_connection
,
args
=
(
client
,
addr
))
thread
=
threading
.
Thread
(
target
=
self
.
handle_connection
,
t
.
daemon
=
True
args
=
(
client
,
addr
))
t
.
start
()
thread
.
daemon
=
True
thread
.
start
()
except
IndexError
as
e
:
except
IndexError
as
e
:
logger
.
error
(
"Start SSH server error: {}"
.
format
(
e
))
logger
.
error
(
"Start SSH server error: {}"
.
format
(
e
))
def
new_connection
(
self
,
addr
,
sock
):
connection
=
Connection
.
new_connection
(
addr
=
addr
,
sock
=
sock
)
self
.
connections
.
append
(
connection
)
return
connection
def
handle_connection
(
self
,
sock
,
addr
):
def
handle_connection
(
self
,
sock
,
addr
):
transport
=
paramiko
.
Transport
(
sock
,
gss_kex
=
False
)
transport
=
paramiko
.
Transport
(
sock
,
gss_kex
=
False
)
try
:
try
:
...
@@ -68,52 +73,52 @@ class SSHServer:
...
@@ -68,52 +73,52 @@ class SSHServer:
transport
.
set_subsystem_handler
(
transport
.
set_subsystem_handler
(
'sftp'
,
paramiko
.
SFTPServer
,
SFTPServer
'sftp'
,
paramiko
.
SFTPServer
,
SFTPServer
)
)
request
=
Request
(
addr
)
connection
=
self
.
new_connection
(
addr
,
sock
=
sock
)
server
=
SSHInterface
(
request
)
server
=
SSHInterface
(
connection
)
try
:
try
:
transport
.
start_server
(
server
=
server
)
transport
.
start_server
(
server
=
server
)
except
paramiko
.
SSHException
:
except
paramiko
.
SSHException
:
logger
.
warning
(
"SSH negotiation failed"
)
logger
.
warning
(
"SSH negotiation failed"
)
return
return
except
EOFError
:
except
EOFError
as
e
:
logger
.
warning
(
"Handle EOF Error
"
)
logger
.
warning
(
"Handle EOF Error
: {}"
.
format
(
e
)
)
return
return
while
transport
.
is_active
():
while
True
:
if
not
transport
.
is_active
():
transport
.
close
()
sock
.
close
()
break
chan
=
transport
.
accept
()
chan
=
transport
.
accept
()
server
.
event
.
wait
(
5
)
server
.
event
.
wait
(
5
)
if
chan
is
None
:
if
chan
is
None
:
continue
continue
if
not
server
.
event
.
is_set
():
if
not
server
.
event
.
is_set
():
logger
.
warning
(
"Client not request a valid request, exiting"
)
logger
.
warning
(
"Client not request a valid request, exiting"
)
sock
.
close
()
return
return
else
:
server
.
event
.
clear
()
t
=
threading
.
Thread
(
target
=
self
.
handle_chan
,
args
=
(
chan
,
request
))
client
=
connection
.
clients
.
get
(
chan
.
get_id
())
client
.
chan
=
chan
t
=
threading
.
Thread
(
target
=
self
.
dispatch
,
args
=
(
client
,))
t
.
daemon
=
True
t
.
daemon
=
True
t
.
start
()
t
.
start
()
Connection
.
remove_connection
(
connection
.
id
)
def
handle_chan
(
self
,
chan
,
request
):
@staticmethod
client
=
Client
(
chan
,
request
)
def
dispatch
(
client
):
current_app
.
add_client
(
client
)
self
.
dispatch
(
client
)
def
dispatch
(
self
,
client
):
supported
=
{
'pty'
,
'x11'
,
'forward-agent'
}
supported
=
{
'pty'
,
'x11'
,
'forward-agent'
}
request_type
=
set
(
client
.
request
.
type
)
chan_type
=
client
.
request
.
type
if
supported
&
request_type
:
kind
=
client
.
request
.
kind
logger
.
info
(
"Request type `pty`, dispatch to interactive mode"
)
if
kind
==
'session'
and
chan_type
in
supported
:
logger
.
info
(
"Request type `{}:{}`, dispatch to interactive mode"
.
format
(
kind
,
chan_type
))
InteractiveServer
(
client
)
.
interact
()
InteractiveServer
(
client
)
.
interact
()
elif
'subsystem'
in
request_type
:
connection
=
Connection
.
get_connection
(
client
.
connection_id
)
connection
.
remove_client
(
client
.
id
)
elif
chan_type
==
'subsystem'
:
pass
pass
else
:
else
:
logger
.
info
(
"Request type `{}`"
.
format
(
request_type
))
msg
=
"Request type `{}:{}` not support now"
.
format
(
kind
,
chan_type
)
client
.
send
(
"Not support request type:
%
s"
%
request_type
)
logger
.
info
(
msg
)
client
.
send
(
msg
)
def
shutdown
(
self
):
def
shutdown
(
self
):
self
.
stop_evt
.
set
()
self
.
stop_evt
.
set
()
coco/
alignmen
t.py
→
coco/
struc
t.py
View file @
8a60c4eb
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
#
#
import
queue
import
queue
import
socket
class
MultiQueueMixin
:
class
MultiQueueMixin
:
...
@@ -24,16 +25,31 @@ class MemoryQueue(MultiQueueMixin, queue.Queue):
...
@@ -24,16 +25,31 @@ class MemoryQueue(MultiQueueMixin, queue.Queue):
pass
pass
def
get_queue
(
config
):
class
SizedList
(
list
):
queue_engine
=
config
[
'QUEUE_ENGINE'
]
def
__init__
(
self
,
maxsize
=
0
):
queue_size
=
config
[
'QUEUE_MAX_SIZE'
]
self
.
maxsize
=
maxsize
self
.
size
=
0
super
()
.
__init__
()
if
queue_engine
==
"server"
:
def
append
(
self
,
b
):
replay_queue
=
MemoryQueue
(
queue_size
)
if
self
.
maxsize
==
0
or
self
.
size
<
self
.
maxsize
:
command_queue
=
MemoryQueue
(
queue_size
)
super
()
.
append
(
b
)
else
:
self
.
size
+=
len
(
b
)
replay_queue
=
MemoryQueue
(
queue_size
)
command_queue
=
MemoryQueue
(
queue_size
)
return
replay_queue
,
command_queue
def
clean
(
self
):
self
.
size
=
0
del
self
[:]
class
SelectEvent
:
def
__init__
(
self
):
self
.
p1
,
self
.
p2
=
socket
.
socketpair
()
def
set
(
self
):
self
.
p2
.
send
(
b
'0'
)
def
fileno
(
self
):
return
self
.
p1
.
fileno
()
def
__getattr__
(
self
,
item
):
return
getattr
(
self
.
p1
,
item
)
coco/tasks.py
View file @
8a60c4eb
...
@@ -2,8 +2,9 @@
...
@@ -2,8 +2,9 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
from
.ctx
import
current_app
,
app_service
from
.ctx
import
app_service
from
.utils
import
get_logger
from
.utils
import
get_logger
from
.session
import
Session
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
...
@@ -18,12 +19,7 @@ class TaskHandler:
...
@@ -18,12 +19,7 @@ class TaskHandler:
def
handle_kill_session
(
task
):
def
handle_kill_session
(
task
):
logger
.
info
(
"Handle kill session task: {}"
.
format
(
task
.
args
))
logger
.
info
(
"Handle kill session task: {}"
.
format
(
task
.
args
))
session_id
=
task
.
args
session_id
=
task
.
args
session
=
None
session
=
Session
.
sessions
.
get
(
session_id
)
for
s
in
current_app
.
sessions
:
if
s
.
id
==
session_id
:
session
=
s
break
if
session
:
if
session
:
session
.
terminate
()
session
.
terminate
()
app_service
.
finish_task
(
task
.
id
)
app_service
.
finish_task
(
task
.
id
)
...
...
coco/utils.py
View file @
8a60c4eb
...
@@ -10,15 +10,21 @@ import os
...
@@ -10,15 +10,21 @@ import os
import
gettext
import
gettext
from
io
import
StringIO
from
io
import
StringIO
from
binascii
import
hexlify
from
binascii
import
hexlify
from
werkzeug.local
import
Local
,
LocalProxy
from
functools
import
partial
,
wraps
import
builtins
import
paramiko
import
paramiko
import
pyte
import
pyte
from
.
import
char
from
.
import
char
from
.c
tx
import
stack
from
.c
onfig
import
config
BASE_DIR
=
os
.
path
.
abspath
(
os
.
path
.
dirname
(
os
.
path
.
dirname
(
__file__
)))
BASE_DIR
=
os
.
path
.
abspath
(
os
.
path
.
dirname
(
os
.
path
.
dirname
(
__file__
)))
APP_NAME
=
"coco"
LOCALE_DIR
=
os
.
path
.
join
(
BASE_DIR
,
'locale'
)
class
Singleton
(
type
):
class
Singleton
(
type
):
def
__init__
(
cls
,
*
args
,
**
kwargs
):
def
__init__
(
cls
,
*
args
,
**
kwargs
):
...
@@ -270,12 +276,6 @@ def sort_assets(assets, order_by='hostname'):
...
@@ -270,12 +276,6 @@ def sort_assets(assets, order_by='hostname'):
return
assets
return
assets
def
_gettext
():
gettext
.
bindtextdomain
(
"coco"
,
os
.
path
.
join
(
BASE_DIR
,
"locale"
))
gettext
.
textdomain
(
"coco"
)
return
gettext
.
gettext
def
get_private_key_fingerprint
(
key
):
def
get_private_key_fingerprint
(
key
):
line
=
hexlify
(
key
.
get_fingerprint
())
line
=
hexlify
(
key
.
get_fingerprint
())
return
b
':'
.
join
([
line
[
i
:
i
+
2
]
for
i
in
range
(
0
,
len
(
line
),
2
)])
return
b
':'
.
join
([
line
[
i
:
i
+
2
]
for
i
in
range
(
0
,
len
(
line
),
2
)])
...
@@ -342,7 +342,7 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0):
...
@@ -342,7 +342,7 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0):
input_data
.
append
(
data
[:
-
1
])
input_data
.
append
(
data
[:
-
1
])
multi_char_with_enter
=
True
multi_char_with_enter
=
True
# If user type ENTER we should get user input
# If user type
s
ENTER we should get user input
if
data
in
char
.
ENTER_CHAR
or
multi_char_with_enter
:
if
data
in
char
.
ENTER_CHAR
or
multi_char_with_enter
:
client
.
send
(
wrap_with_line_feed
(
b
''
,
after
=
2
))
client
.
send
(
wrap_with_line_feed
(
b
''
,
after
=
2
))
option
=
parser
.
parse_input
(
input_data
)
option
=
parser
.
parse_input
(
input_data
)
...
@@ -356,14 +356,6 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0):
...
@@ -356,14 +356,6 @@ def net_input(client, prompt='Opt> ', sensitive=False, before=0, after=0):
input_data
.
append
(
data
)
input_data
.
append
(
data
)
def
register_app
(
app
):
stack
[
'app'
]
=
app
def
register_service
(
service
):
stack
[
'service'
]
=
service
zh_pattern
=
re
.
compile
(
r'[\u4e00-\u9fa5]'
)
zh_pattern
=
re
.
compile
(
r'[\u4e00-\u9fa5]'
)
...
@@ -419,5 +411,62 @@ def int_length(i):
...
@@ -419,5 +411,62 @@ def int_length(i):
return
len
(
str
(
i
))
return
len
(
str
(
i
))
ugettext
=
_gettext
()
def
_get_trans
():
gettext
.
install
(
APP_NAME
,
LOCALE_DIR
)
zh
=
gettext
.
translation
(
APP_NAME
,
LOCALE_DIR
,
[
"zh_CN"
])
en
=
gettext
.
translation
(
APP_NAME
,
LOCALE_DIR
,
[
"en"
])
return
zh
,
en
trans_zh
,
trans_en
=
_get_trans
()
_thread_locals
=
Local
()
def
set_current_lang
(
lang
):
setattr
(
_thread_locals
,
'LANGUAGE_CODE'
,
lang
)
def
get_current_lang
(
attr
):
return
getattr
(
_thread_locals
,
attr
,
None
)
def
_gettext
(
lang
):
if
lang
==
'en'
:
trans_en
.
install
()
else
:
trans_zh
.
install
()
return
builtins
.
__dict__
[
'_'
]
def
_find
(
attr
):
lang
=
get_current_lang
(
attr
)
if
lang
is
None
:
lang
=
config
[
'LANGUAGE_CODE'
]
set_current_lang
(
lang
)
return
_gettext
(
lang
)
def
switch_lang
():
lang
=
get_current_lang
(
'LANGUAGE_CODE'
)
if
lang
==
'zh'
:
set_current_lang
(
'en'
)
elif
lang
==
'en'
:
set_current_lang
(
'zh'
)
logger
=
get_logger
(
__file__
)
def
ignore_error
(
func
):
@wraps
(
func
)
def
wrapper
(
*
args
,
**
kwargs
):
try
:
resp
=
func
(
*
args
,
**
kwargs
)
return
resp
except
Exception
as
e
:
logger
.
error
(
"Error occur: {} {}"
.
format
(
func
.
__name__
,
e
))
raise
e
return
wrapper
ugettext
=
LocalProxy
(
partial
(
_find
,
'LANGUAGE_CODE'
))
conf_example.py
View file @
8a60c4eb
...
@@ -54,6 +54,12 @@ class Config:
...
@@ -54,6 +54,12 @@ class Config:
# 登录是否支持秘钥认证
# 登录是否支持秘钥认证
# PUBLIC_KEY_AUTH = True
# PUBLIC_KEY_AUTH = True
# SSH白名单
# ALLOW_SSH_USER = 'all' # ['test', 'test2']
# SSH黑名单, 如果用户同时在白名单和黑名单,黑名单优先生效
# BLOCK_SSH_USER = []
# 和Jumpserver 保持心跳时间间隔
# 和Jumpserver 保持心跳时间间隔
# HEARTBEAT_INTERVAL = 5
# HEARTBEAT_INTERVAL = 5
...
@@ -66,5 +72,11 @@ class Config:
...
@@ -66,5 +72,11 @@ class Config:
"TYPE"
:
"server"
"TYPE"
:
"server"
}
}
# SSH连接超时时间 (default 15 seconds)
# SSH_TIMEOUT = 15
# 语言 = en
LANGUAGE_CODE
=
'zh'
config
=
Config
()
config
=
Config
()
locale/en/LC_MESSAGES/coco.mo
0 → 100644
View file @
8a60c4eb
File added
locale/en/LC_MESSAGES/coco.po
0 → 100644
View file @
8a60c4eb
# Language locale/en/LC translations for PACKAGE package.
# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# BaiJiangjie <bugatti_it@163.com>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-03 10:39+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"
"Language: locale/en/LC_MESSAGES/coco\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:147
msgid "Connect idle more than {} minutes, disconnect"
msgstr ""
#: coco/interactive.py:61
#, python-brace-format
msgid ""
"\n"
"{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system "
"{end}{R}{R}"
msgstr ""
#: coco/interactive.py:63
#, 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:64
#, 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:65
#, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr ""
#: coco/interactive.py:66
#, python-brace-format
msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr ""
#: coco/interactive.py:67
#, python-brace-format
msgid ""
"{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under "
"the node, such as g1.{R}"
msgstr ""
#: coco/interactive.py:68
#, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr ""
#: coco/interactive.py:69
#, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr ""
#: coco/interactive.py:70
#, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr ""
#: coco/interactive.py:142
msgid "No"
msgstr ""
#: coco/interactive.py:149
msgid "Name"
msgstr ""
#: coco/interactive.py:149
msgid "Assets"
msgstr ""
#: coco/interactive.py:155
msgid "Total: {}"
msgstr ""
#: coco/interactive.py:159
msgid "There is no matched node, please re-enter"
msgstr ""
#: coco/interactive.py:170
msgid "ID"
msgstr ""
#: coco/interactive.py:170
msgid "Hostname"
msgstr ""
#: coco/interactive.py:170
msgid "IP"
msgstr ""
#: coco/interactive.py:170
msgid "LoginAs"
msgstr ""
#: coco/interactive.py:184
msgid "Comment"
msgstr ""
#: coco/interactive.py:192
msgid "Total: {} Match: {}"
msgstr ""
#: coco/interactive.py:235
msgid "Select a login:: "
msgstr ""
#: coco/interactive.py:258
msgid ""
"Terminal does not support login Windows, please use web terminal to access"
msgstr ""
#: coco/interactive.py:269
msgid "No system user"
msgstr ""
#: coco/proxy.py:88
msgid "No permission"
msgstr ""
#: coco/proxy.py:130
msgid "Connecting to {}@{} {:.1f}"
msgstr ""
#: coco/session.py:143
msgid "Terminated by administrator"
msgstr ""
locale/en/LC_MESSAGES/coco.po~
0 → 100644
View file @
8a60c4eb
# Language locale/en/LC translations for PACKAGE package.
# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# BaiJiangjie <bugatti_it@163.com>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-03 10:36+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"
"Language: locale/en/LC_MESSAGES/coco\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:147
msgid "Connect idle more than {} minutes, disconnect"
msgstr ""
#: coco/interactive.py:61
#, python-brace-format
msgid ""
"\n"
"{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system "
"{end}{R}{R}"
msgstr ""
#: coco/interactive.py:63
#, 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:64
#, 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:65
#, python-brace-format
msgid "{T}3) Enter {green}p{end} to display the host you have permission.{R}"
msgstr ""
#: coco/interactive.py:66
#, python-brace-format
msgid ""
"{T}4) Enter {green}g{end} to display the node that you have permission.{R}"
msgstr ""
#: coco/interactive.py:67
#, python-brace-format
msgid ""
"{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under "
"the node, such as g1.{R}"
msgstr ""
#: coco/interactive.py:68
#, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr ""
#: coco/interactive.py:69
#, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr ""
#: coco/interactive.py:70
#, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr ""
#: coco/interactive.py:142
msgid "No"
msgstr ""
#: coco/interactive.py:149
msgid "Name"
msgstr ""
#: coco/interactive.py:149
msgid "Assets"
msgstr ""
#: coco/interactive.py:155
msgid "Total: {}"
msgstr ""
#: coco/interactive.py:159
msgid "There is no matched node, please re-enter"
msgstr ""
#: coco/interactive.py:170
msgid "ID"
msgstr ""
#: coco/interactive.py:170
msgid "Hostname"
msgstr ""
#: coco/interactive.py:170
msgid "IP"
msgstr ""
#: coco/interactive.py:170
msgid "LoginAs"
msgstr ""
#: coco/interactive.py:184
msgid "Comment"
msgstr ""
#: coco/interactive.py:192
msgid "Total: {} Match: {}"
msgstr ""
#: coco/interactive.py:235
msgid "Select a login:: "
msgstr ""
#: coco/interactive.py:258
msgid ""
"Terminal does not support login Windows, please use web terminal to access"
msgstr ""
#: coco/interactive.py:269
msgid "No system user"
msgstr ""
#: coco/session.py:143
msgid "Terminated by administrator"
msgstr ""
locale/zh_CN/LC_MESSAGES/coco.mo
0 → 100644
View file @
8a60c4eb
File added
locale/zh_CN/LC_MESSAGES/coco.po
0 → 100644
View file @
8a60c4eb
# Language locale/zh translations for PACKAGE package.
# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# BaiJiangjie <bugatti_it@163.com>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-03 10:39+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"
"Language: locale/zh_CN/LC_MESSAGES/coco\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:147
msgid "Connect idle more than {} minutes, disconnect"
msgstr "空闲时间超过 {} 分钟,断开连接"
#: coco/interactive.py:61
#, python-brace-format
msgid ""
"\n"
"{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system "
"{end}{R}{R}"
msgstr ""
"\n"
"{T}{T}{title} {user}, 欢迎使用Jumpserver开源跳板机系统 {end}{R}{R}"
#: coco/interactive.py:63
#, 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 ""
"{T}1) 输入 {green}ID{end} 直接登录 或 输入{green}部分 IP,主机名,备注{end} 进"
"行搜索登录(如果唯一).{R}"
#: coco/interactive.py:64
#, python-brace-format
msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
"search, such as: /ip.{R}"
msgstr ""
"{T}2) 输入 {green}/{end} + {green}IP, 主机名{end} or {green}备注 {end}搜索. "
"如: /ip{R}"
#: coco/interactive.py:65
#, 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:66
#, 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:67
#, python-brace-format
msgid ""
"{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under "
"the node, such as g1.{R}"
msgstr "{T}5) 输入 {green}g{end} + {green}组ID{end} 显示节点下主机. 如: g1{R}"
#: coco/interactive.py:68
#, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr "{T}6) 输入 {green}s{end} 中/英文切换.{R}"
#: coco/interactive.py:69
#, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr "{T}7) 输入 {green}h{end} 帮助.{R}"
#: coco/interactive.py:70
#, fuzzy, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr "{T}0) 输入 {green}q{end} 退出.{R}\n"
#: coco/interactive.py:142
msgid "No"
msgstr "无"
#: coco/interactive.py:149
msgid "Name"
msgstr "名称"
#: coco/interactive.py:149
msgid "Assets"
msgstr "资产"
#: coco/interactive.py:155
msgid "Total: {}"
msgstr "总共: {}"
#: coco/interactive.py:159
msgid "There is no matched node, please re-enter"
msgstr "没有匹配分组,请重新输入"
#: coco/interactive.py:170
msgid "ID"
msgstr ""
#: coco/interactive.py:170
msgid "Hostname"
msgstr "主机名"
#: coco/interactive.py:170
msgid "IP"
msgstr ""
#: coco/interactive.py:170
msgid "LoginAs"
msgstr ""
#: coco/interactive.py:184
msgid "Comment"
msgstr "备注"
#: coco/interactive.py:192
msgid "Total: {} Match: {}"
msgstr "总共: {} 匹配: {}"
#: coco/interactive.py:235
msgid "Select a login:: "
msgstr "选择一个登录:"
#: coco/interactive.py:258
msgid ""
"Terminal does not support login Windows, please use web terminal to access"
msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:269
msgid "No system user"
msgstr "没有系统用户"
#: coco/proxy.py:88
msgid "No permission"
msgstr "没有权限"
#: coco/proxy.py:130
msgid "Connecting to {}@{} {:.1f}"
msgstr "开始连接到 {}@{} {:.1f}"
#: coco/session.py:143
msgid "Terminated by administrator"
msgstr "被管理员中断"
locale/zh_CN/LC_MESSAGES/coco.po~
0 → 100644
View file @
8a60c4eb
# Language locale/zh translations for PACKAGE package.
# Copyright (C) 2018 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# BaiJiangjie <bugatti_it@163.com>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-03 10:36+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"
"Language: locale/zh_CN/LC_MESSAGES/coco\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: coco/app.py:147
msgid "Connect idle more than {} minutes, disconnect"
msgstr "空闲时间超过 {} 分钟,断开连接"
#: coco/interactive.py:61
#, python-brace-format
msgid ""
"\n"
"{T}{T}{title} {user}, Welcome to use Jumpserver open source fortress system "
"{end}{R}{R}"
msgstr ""
"\n"
"{T}{T}{title} {user}, 欢迎使用Jumpserver开源跳板机系统 {end}{R}{R}"
#: coco/interactive.py:63
#, 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 ""
"{T}1) 输入 {green}ID{end} 直接登录 或 输入{green}部分 IP,主机名,备注{end} 进"
"行搜索登录(如果唯一).{R}"
#: coco/interactive.py:64
#, python-brace-format
msgid ""
"{T}2) Enter {green}/{end} + {green}IP, Hostname{end} or {green}Comment {end} "
"search, such as: /ip.{R}"
msgstr ""
"{T}2) 输入 {green}/{end} + {green}IP, 主机名{end} or {green}备注 {end}搜索. "
"如: /ip{R}"
#: coco/interactive.py:65
#, 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:66
#, 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:67
#, python-brace-format
msgid ""
"{T}5) Enter {green}g{end} + {green}Group ID{end} to display the host under "
"the node, such as g1.{R}"
msgstr "{T}5) 输入 {green}g{end} + {green}组ID{end} 显示节点下主机. 如: g1{R}"
#: coco/interactive.py:68
#, python-brace-format
msgid "{T}6) Enter {green}s{end} Chinese-english switch.{R}"
msgstr "{T}6) 输入 {green}s{end} 中/英文切换.{R}"
#: coco/interactive.py:69
#, python-brace-format
msgid "{T}7) Enter {green}h{end} help.{R}"
msgstr "{T}7) 输入 {green}h{end} 帮助.{R}"
#: coco/interactive.py:70
#, fuzzy, python-brace-format
msgid "{T}0) Enter {green}q{end} exit.{R}"
msgstr "{T}0) 输入 {green}q{end} 退出.{R}\n"
#: coco/interactive.py:142
msgid "No"
msgstr "无"
#: coco/interactive.py:149
msgid "Name"
msgstr "名称"
#: coco/interactive.py:149
msgid "Assets"
msgstr "资产"
#: coco/interactive.py:155
msgid "Total: {}"
msgstr "总共: {}"
#: coco/interactive.py:159
msgid "There is no matched node, please re-enter"
msgstr "没有匹配分组,请重新输入"
#: coco/interactive.py:170
msgid "ID"
msgstr ""
#: coco/interactive.py:170
msgid "Hostname"
msgstr "主机名"
#: coco/interactive.py:170
msgid "IP"
msgstr ""
#: coco/interactive.py:170
msgid "LoginAs"
msgstr ""
#: coco/interactive.py:184
msgid "Comment"
msgstr "备注"
#: coco/interactive.py:192
msgid "Total: {} Match: {}"
msgstr "总共: {} 匹配: {}"
#: coco/interactive.py:235
msgid "Select a login:: "
msgstr "选择一个登录:"
#: coco/interactive.py:258
msgid ""
"Terminal does not support login Windows, please use web terminal to access"
msgstr "终端不支持登录windows, 请使用web terminal访问"
#: coco/interactive.py:269
msgid "No system user"
msgstr "没有系统用户"
#: coco/session.py:143
msgid "Terminated by administrator"
msgstr "被管理员中断"
requirements/requirements.txt
View file @
8a60c4eb
...
@@ -19,7 +19,7 @@ itsdangerous==0.24
...
@@ -19,7 +19,7 @@ itsdangerous==0.24
Jinja2==2.10
Jinja2==2.10
jmespath==0.9.3
jmespath==0.9.3
jms-storage==0.0.18
jms-storage==0.0.18
jumpserver-python-sdk==0.0.4
7
jumpserver-python-sdk==0.0.4
8
MarkupSafe==1.0
MarkupSafe==1.0
oss2==2.4.0
oss2==2.4.0
paramiko==2.4.1
paramiko==2.4.1
...
...
requirements/rpm_requirements.txt
View file @
8a60c4eb
libffi-devel sshpass
libffi-devel sshpass krb5-devel
\ No newline at end of file
utils/messages.sh
0 → 100755
View file @
8a60c4eb
#!/bin/bash
#
function
init_message
()
{
xgettext
-k_
-o
pot/coco.pot
--from-code
=
UTF-8 coco/
*
.py
msginit
-l
locale/zh_CN/LC_MESSAGES/coco
-i
pot/coco.pot
msginit
-l
locale/en/LC_MESSAGES/coco
-i
pot/coco.pot
}
function
make_message
()
{
xgettext
-k_
-o
pot/coco.pot
--from-code
=
UTF-8 coco/
*
.py
msgmerge
-U
locale/zh_CN/LC_MESSAGES/coco.po pot/coco.pot
msgmerge
-U
locale/en/LC_MESSAGES/coco.po pot/coco.pot
}
function
compile_message
()
{
msgfmt
-o
locale/zh_CN/LC_MESSAGES/coco.mo locale/zh_CN/LC_MESSAGES/coco.po
msgfmt
-o
locale/en/LC_MESSAGES/coco.mo locale/en/LC_MESSAGES/coco.po
}
action
=
$1
if
[
-z
"
$action
"
]
;
then
action
=
"make"
fi
case
$action
in
m|make
)
make_message
;;
i|init
)
init_message
;;
c|compile
)
compile_message
;;
*
)
echo
"Usage:
$0
[m|make i|init | c|compile]"
exit
1
;;
esac
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