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
f2e5c268
Commit
f2e5c268
authored
Oct 17, 2017
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Feature] 添加ssh interface
parent
ecf43f40
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
297 additions
and
30 deletions
+297
-30
app.py
coco/app.py
+25
-5
char.py
coco/char.py
+7
-0
interactive.py
coco/interactive.py
+46
-0
interface.py
coco/interface.py
+56
-17
sshd.py
coco/sshd.py
+95
-8
utils.py
coco/utils.py
+65
-0
conf_example.py
conf_example.py
+3
-0
No files found.
coco/app.py
View file @
f2e5c268
import
os
import
time
import
threading
from
queue
import
Queue
from
.config
import
Config
from
.sshd
import
SSHServer
...
...
@@ -30,16 +31,24 @@ class Coco:
'SSH_PASSWORD_AUTH'
:
True
,
'SSH_PUBLIC_KEY_AUTH'
:
True
,
'HEARTBEAT_INTERVAL'
:
5
,
'MAX_CONNECTIONS'
:
500
,
}
def
__init__
(
self
,
name
=
None
):
def
__init__
(
self
,
name
=
None
,
root_path
=
None
):
self
.
config
=
self
.
config_class
(
BASE_DIR
,
defaults
=
self
.
default_config
)
self
.
sessions
=
[]
self
.
connections
=
[]
self
.
sshd
=
SSHServer
(
self
)
self
.
ws
=
None
if
name
:
self
.
name
=
name
else
:
self
.
name
=
self
.
config
[
'NAME'
]
if
root_path
is
None
:
self
.
make_logger
()
def
make_logger
(
self
):
...
...
@@ -61,20 +70,31 @@ class Coco:
'host'
:
self
.
config
[
'BIND_HOST'
],
'port'
:
self
.
config
[
'WS_PORT'
]})
print
(
'Quit the server with CONTROL-C.'
)
exit_queue
=
Queue
()
try
:
self
.
run_sshd
()
self
.
run_ws
()
if
self
.
config
[
"SSHD_PORT"
]
!=
0
:
self
.
run_sshd
()
if
self
.
config
[
'WS_PORT'
]
!=
0
:
self
.
run_ws
()
if
exit_queue
.
get
():
self
.
shutdown
()
except
KeyboardInterrupt
:
self
.
shutdown
()
def
run_sshd
(
self
):
thread
=
threading
.
Thread
(
target
=
SSHServer
(
self
)
.
run
,
args
=
())
thread
=
threading
.
Thread
(
target
=
self
.
sshd
.
run
,
args
=
())
thread
.
daemon
=
True
thread
.
start
()
def
run_ws
(
self
):
pass
def
shutdown
(
self
):
pass
print
(
"Grace shutdown the server"
)
self
.
sshd
.
shutdown
()
def
monitor_session
(
self
):
pass
...
...
coco/char.py
0 → 100644
View file @
f2e5c268
#!coding: utf-8
BACKSPACE_CHAR
=
{
b
'
\x08
'
:
b
'
\x08\x1b
[K'
,
b
'
\x7f
'
:
b
'
\x08\x1b
[K'
}
ENTER_CHAR
=
[
b
'
\r
'
,
b
'
\n
'
,
b
'
\r\n
'
]
UNSUPPORTED_CHAR
=
{
b
'
\x15
'
:
'Ctrl-U'
,
b
'
\x0c
'
:
'Ctrl-L'
,
b
'
\x05
'
:
'Ctrl-E'
}
CLEAR_CHAR
=
b
'
\x1b
[H
\x1b
[2J'
BELL_CHAR
=
b
'
\x07
'
coco/interactive.py
View file @
f2e5c268
#!coding: utf-8
import
socket
from
.
import
char
class
InteractiveServer
:
def
__init__
(
self
,
app
,
request
,
chan
):
self
.
app
=
app
self
.
request
=
request
self
.
client
=
chan
def
display_banner
(
self
):
self
.
client
.
send
(
char
.
CLEAR_CHAR
)
banner
=
u"""
\n\033
[1;32m
%
s, 欢迎使用Jumpserver开源跳板机系统
\033
[0m
\r\n\r
1) 输入
\033
[32mID
\033
[0m 直接登录 或 输入
\033
[32m部分 IP,主机名,备注
\033
[0m 进行搜索登录(如果唯一).
\r
2) 输入
\033
[32m/
\033
[0m +
\033
[32mIP, 主机名 or 备注
\033
[0m搜索. 如: /ip
\r
3) 输入
\033
[32mP/p
\033
[0m 显示您有权限的主机.
\r
4) 输入
\033
[32mG/g
\033
[0m 显示您有权限的主机组.
\r
5) 输入
\033
[32mG/g
\033
[0m
\033
[0m +
\033
[32m组ID
\033
[0m 显示该组下主机. 如: g1
\r
6) 输入
\033
[32mE/e
\033
[0m 批量执行命令.(未完成)
\r
7) 输入
\033
[32mU/u
\033
[0m 批量上传文件.(未完成)
\r
8) 输入
\033
[32mD/d
\033
[0m 批量下载文件.(未完成)
\r
9) 输入
\033
[32mH/h
\033
[0m 帮助.
\r
0) 输入
\033
[32mQ/q
\033
[0m 退出.
\r\n
"""
%
self
.
request
.
user
self
.
client
.
send
(
banner
)
def
get_input
(
self
,
prompt
=
'Opt> '
):
pass
def
dispatch
(
self
):
pass
def
run
(
self
):
self
.
display_banner
()
while
True
:
try
:
self
.
dispatch
()
except
socket
.
error
:
break
self
.
close
()
def
close
(
self
):
pass
coco/interface.py
View file @
f2e5c268
...
...
@@ -2,6 +2,7 @@
import
logging
import
paramiko
import
threading
logger
=
logging
.
getLogger
(
__file__
)
...
...
@@ -15,12 +16,13 @@ class SSHInterface(paramiko.ServerInterface):
https://github.com/paramiko/paramiko/blob/master/demos/demo_server.py
"""
def
__init__
(
self
,
app
,
*
args
,
*
kwargs
):
def
__init__
(
self
,
app
,
request
):
self
.
app
=
app
self
.
request
=
request
self
.
event
=
threading
.
Event
()
def
check_auth_interactive
(
self
,
username
,
submethods
):
"""
:param username: the username of the authenticating client
:param submethods: a comma-separated list of methods preferred
by the client (usually empty)
...
...
@@ -32,6 +34,7 @@ class SSHInterface(paramiko.ServerInterface):
def
check_auth_interactive_response
(
self
,
responses
):
logger
.
info
(
"Check auth interactive response:
%
s "
%
responses
)
# TODO:MFA Auth
pass
def
enable_auth_gssapi
(
self
):
...
...
@@ -52,32 +55,52 @@ class SSHInterface(paramiko.ServerInterface):
def
validate_auth
(
self
,
username
,
password
=
""
,
key
=
""
):
# Todo: Implement it
self
.
request
.
user
=
"guang"
return
True
def
check_channel_direct_tcpip_request
(
self
,
chanid
,
origin
,
destination
):
logger
.
info
(
"Check channel direct tcpip request:
%
d
%
s
%
s"
%
logger
.
debug
(
"Check channel direct tcpip request:
%
d
%
s
%
s"
%
(
chanid
,
origin
,
destination
))
self
.
request
.
type
=
'direct-tcpip'
self
.
request
.
meta
=
{
'chanid'
:
chanid
,
'origin'
:
origin
,
'destination'
:
destination
,
}
self
.
event
.
set
()
return
0
def
check_channel_env_request
(
self
,
channel
,
name
,
value
):
logger
.
info
(
"Check channel env request:
%
s,
%
s,
%
s"
%
(
channel
,
name
,
value
))
logger
.
debug
(
"Check channel env request:
%
s,
%
s,
%
s"
%
(
channel
,
name
,
value
))
return
False
def
check_channel_exec_request
(
self
,
channel
,
command
):
logger
.
info
(
"Check channel exec request:
%
s `
%
s`"
%
(
channel
,
command
))
logger
.
debug
(
"Check channel exec request:
%
s `
%
s`"
%
(
channel
,
command
))
self
.
request
.
type
=
'exec'
self
.
request
.
meta
=
{
'channel'
:
channel
,
'command'
:
command
}
self
.
event
.
set
()
return
False
def
check_channel_forward_agent_request
(
self
,
channel
):
logger
.
info
(
"Check channel forward agent request:
%
s"
%
channel
)
logger
.
debug
(
"Check channel forward agent request:
%
s"
%
channel
)
self
.
request
.
type
=
"forward-agent"
self
.
request
.
meta
=
{
'channel'
:
channel
}
self
.
event
.
set
()
return
False
def
check_channel_pty_request
(
self
,
channel
,
term
,
width
,
height
,
pixelwidth
,
pixelheight
,
modes
):
logger
.
info
(
"Check channel pty request:
%
s
%
s
%
s
%
s
%
s
%
s
%
s"
%
(
channel
,
term
,
width
,
height
,
pixelwidth
,
pixelheight
,
modes
))
logger
.
debug
(
"Check channel pty request:
%
s
%
s
%
s
%
s
%
s
%
s"
%
(
channel
,
term
,
width
,
height
,
pixelwidth
,
pixelheight
))
self
.
request
.
type
=
'pty'
self
.
request
.
meta
=
{
'channel'
:
channel
,
'term'
:
term
,
'width'
:
width
,
'height'
:
height
,
'pixelwidth'
:
pixelwidth
,
'pixelheight'
:
pixelheight
,
'models'
:
modes
,
}
self
.
event
.
set
()
return
True
def
check_channel_request
(
self
,
kind
,
chanid
):
...
...
@@ -90,23 +113,39 @@ class SSHInterface(paramiko.ServerInterface):
def
check_channel_subsystem_request
(
self
,
channel
,
name
):
logger
.
info
(
"Check channel subsystem request:
%
s
%
s"
%
(
channel
,
name
))
self
.
request
.
type
=
'subsystem'
self
.
request
.
meta
=
{
'channel'
:
channel
,
'name'
:
name
}
self
.
event
.
set
()
return
False
def
check_channel_window_change_request
(
self
,
channel
,
width
,
height
,
pixelwidth
,
pixelheight
):
# Todo: implement window size change
def
check_channel_window_change_request
(
self
,
channel
,
width
,
height
,
pixelwidth
,
pixelheight
):
self
.
request
.
meta
[
'width'
]
=
width
self
.
request
.
meta
[
'height'
]
=
height
self
.
request
.
meta
[
'pixelwidth'
]
=
pixelwidth
self
.
request
.
meta
[
'pixelheight'
]
=
pixelheight
self
.
request
.
change_size_event
.
set
()
return
True
def
check_channel_x11_request
(
self
,
channel
,
single_connection
,
auth_protocol
,
auth_cookie
,
screen_number
):
def
check_channel_x11_request
(
self
,
channel
,
single_connection
,
auth_protocol
,
auth_cookie
,
screen_number
):
logger
.
info
(
"Check channel x11 request
%
s
%
s
%
s
%
s
%
s"
%
(
channel
,
single_connection
,
auth_protocol
,
auth_cookie
,
screen_number
))
self
.
request
.
type
=
'x11'
self
.
request
.
meta
=
{
'channel'
:
channel
,
'single_connection'
:
single_connection
,
'auth_protocol'
:
auth_protocol
,
'auth_cookie'
:
auth_cookie
,
'screen_number'
:
screen_number
,
}
self
.
event
.
set
()
return
False
def
check_port_forward_request
(
self
,
address
,
port
):
logger
.
info
(
"Check channel subsystem request:
%
s
%
s"
%
(
address
,
port
))
logger
.
info
(
"Check channel port forward request:
%
s
%
s"
%
(
address
,
port
))
self
.
request
.
type
=
'port-forward'
self
.
request
.
meta
=
{
'address'
:
address
,
'port'
:
port
}
self
.
event
.
set
()
return
False
def
get_banner
(
self
):
...
...
coco/sshd.py
View file @
f2e5c268
#! coding: utf-8
import
os
import
logging
import
socket
import
threading
import
paramiko
import
sys
from
.utils
import
ssh_key_gen
from
.interface
import
SSHInterface
logger
=
logging
.
getLogger
(
__file__
)
BACKLOG
=
5
class
SSHServer
:
class
Request
:
def
__init__
(
self
,
client
,
addr
):
self
.
type
=
""
self
.
meta
=
{}
self
.
client
=
client
self
.
addr
=
addr
self
.
user
=
None
self
.
change_size_event
=
threading
.
Event
()
self
.
win_size
=
{}
class
SSHServer
:
def
__init__
(
self
,
app
=
None
):
self
.
app
=
app
self
.
stop_event
=
threading
.
Event
()
self
.
sock
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
self
.
host_key_path
=
os
.
path
.
join
(
self
.
app
.
root_path
,
'keys'
,
'host_rsa_key'
)
self
.
host_key
=
self
.
get_host_key
()
def
ru
n
(
self
):
def
liste
n
(
self
):
host
=
self
.
app
.
config
[
"BIND_HOST"
]
port
=
self
.
app
.
config
[
"SSHD_PORT"
]
sock
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
sock
.
setsockopt
(
socket
.
SOL_SOCKET
,
socket
.
SO_REUSEADDR
,
1
)
sock
.
bind
((
host
,
port
))
sock
.
listen
(
BACKLOG
)
print
(
'Starting ssh server at
%(host)
s:
%(port)
s'
%
print
(
'Starting shh server at
%(host)
s:
%(port)
s'
%
{
"host"
:
host
,
"port"
:
port
})
self
.
sock
.
setsockopt
(
socket
.
SOL_SOCKET
,
socket
.
SO_REUSEADDR
,
1
)
self
.
sock
.
bind
((
host
,
port
))
self
.
sock
.
listen
(
BACKLOG
)
def
get_host_key
(
self
):
if
not
os
.
path
.
isfile
(
self
.
host_key_path
):
self
.
gen_host_key
()
return
paramiko
.
RSAKey
(
filename
=
self
.
host_key_path
)
def
gen_host_key
(
self
):
ssh_key
,
_
=
ssh_key_gen
()
with
open
(
self
.
host_key_path
,
'w'
)
as
f
:
f
.
write
(
ssh_key
)
def
run
(
self
):
self
.
listen
()
max_conn_num
=
self
.
app
[
'MAX_CONNECTIONS'
]
while
not
self
.
stop_event
.
is_set
():
try
:
client
,
addr
=
self
.
sock
.
accept
()
logger
.
info
(
"Get ssh request from
%
s:
%
s"
%
(
addr
[
0
],
addr
[
1
]))
if
len
(
self
.
app
.
connections
)
>=
max_conn_num
:
client
.
close
()
logger
.
warning
(
"Arrive max connection number
%
s, "
"reject new request
%
s:
%
s"
%
(
max_conn_num
,
addr
[
0
],
addr
[
1
]))
else
:
self
.
app
.
connections
.
append
((
client
,
addr
))
thread
=
threading
.
Thread
(
target
=
self
.
handle
,
args
=
(
client
,
addr
))
thread
.
daemon
=
True
thread
.
start
()
except
Exception
as
e
:
logger
.
error
(
"SSH server error:
%
s"
%
e
)
def
handle
(
self
,
client
,
addr
):
transport
=
paramiko
.
Transport
(
client
,
gss_kex
=
False
)
try
:
transport
.
load_server_moduli
()
except
IOError
:
logger
.
warning
(
"Failed load moduli -- gex will be unsupported"
)
transport
.
add_server_key
(
self
.
host_key
)
request
=
Request
(
client
,
addr
)
server
=
SSHInterface
(
self
.
app
,
request
)
try
:
transport
.
start_server
(
server
=
server
)
except
paramiko
.
SSHException
:
logger
.
warning
(
"SSH negotiation failed."
)
sys
.
exit
(
1
)
chan
=
transport
.
accept
(
10
)
if
chan
is
None
:
logger
.
warning
(
"No ssh channel get"
)
sys
.
exit
(
1
)
server
.
event
.
wait
(
5
)
if
not
server
.
event
.
is_set
():
logger
.
warning
(
"Client not request a valid request"
)
sys
.
exit
(
2
)
self
.
dispatch
(
request
,
chan
)
def
dispatch
(
self
,
request
,
channel
):
if
request
.
type
==
'pty'
:
pass
elif
request
.
type
==
'exec'
:
pass
elif
request
.
type
==
'subsystem'
:
pass
else
:
channel
.
send
(
"Not support request type:
%
s"
%
request
.
type
)
def
shutdown
(
self
):
pass
self
.
stop_event
.
set
()
coco/utils.py
View file @
f2e5c268
#!coding: utf-8
import
os
import
paramiko
from
io
import
StringIO
def
ssh_key_string_to_obj
(
text
):
key_f
=
StringIO
(
text
)
key
=
None
try
:
key
=
paramiko
.
RSAKey
.
from_private_key
(
key_f
)
except
paramiko
.
SSHException
:
pass
try
:
key
=
paramiko
.
DSSKey
.
from_private_key
(
key_f
)
except
paramiko
.
SSHException
:
pass
return
key
def
ssh_pubkey_gen
(
private_key
=
None
,
username
=
'jumpserver'
,
hostname
=
'localhost'
):
if
isinstance
(
private_key
,
str
):
private_key
=
ssh_key_string_to_obj
(
private_key
)
if
not
isinstance
(
private_key
,
(
paramiko
.
RSAKey
,
paramiko
.
DSSKey
)):
raise
IOError
(
'Invalid private key'
)
public_key
=
"
%(key_type)
s
%(key_content)
s
%(username)
s@
%(hostname)
s"
%
{
'key_type'
:
private_key
.
get_name
(),
'key_content'
:
private_key
.
get_base64
(),
'username'
:
username
,
'hostname'
:
hostname
,
}
return
public_key
def
ssh_key_gen
(
length
=
2048
,
type
=
'rsa'
,
password
=
None
,
username
=
'jumpserver'
,
hostname
=
None
):
"""Generate user ssh private and public key
Use paramiko RSAKey generate it.
:return private key str and public key str
"""
if
hostname
is
None
:
hostname
=
os
.
uname
()[
1
]
f
=
StringIO
()
try
:
if
type
==
'rsa'
:
private_key_obj
=
paramiko
.
RSAKey
.
generate
(
length
)
elif
type
==
'dsa'
:
private_key_obj
=
paramiko
.
DSSKey
.
generate
(
length
)
else
:
raise
IOError
(
'SSH private key must be `rsa` or `dsa`'
)
private_key_obj
.
write_private_key
(
f
,
password
=
password
)
private_key
=
f
.
getvalue
()
public_key
=
ssh_pubkey_gen
(
private_key_obj
,
username
=
username
,
hostname
=
hostname
)
return
private_key
,
public_key
except
IOError
:
raise
IOError
(
'These is error when generate ssh key.'
)
\ No newline at end of file
conf_example.py
View file @
f2e5c268
...
...
@@ -17,6 +17,9 @@ APP_NAME = "coco"
# 监听的WS端口号,默认5000
# WS_PORT = 5000
# 最大连接线程数
# MAX_CONNECTIONS = 500
# 是否开启DEBUG
# DEBUG = True
...
...
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