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
f46cd8cd
Commit
f46cd8cd
authored
Jul 02, 2018
by
ibuler
Browse files
Options
Browse Files
Download
Plain Diff
[Merge] 修改冲突
parents
518a92d0
26b5ad36
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
341 additions
and
8 deletions
+341
-8
connection.py
coco/connection.py
+137
-1
models.py
coco/models.py
+171
-0
proxy.py
coco/proxy.py
+32
-6
requirements.txt
requirements/requirements.txt
+1
-1
No files found.
coco/connection.py
View file @
f46cd8cd
...
...
@@ -2,16 +2,22 @@
#
import
os
import
re
import
socket
import
selectors
import
telnetlib
import
paramiko
from
paramiko.ssh_exception
import
SSHException
from
.ctx
import
app_service
from
.utils
import
get_logger
,
get_private_key_fingerprint
from
.utils
import
get_logger
,
get_private_key_fingerprint
,
net_input
logger
=
get_logger
(
__file__
)
TIMEOUT
=
10
BUF_SIZE
=
1024
MANUAL_LOGIN
=
'manual'
AUTO_LOGIN
=
'auto'
class
SSHConnection
:
...
...
@@ -149,3 +155,133 @@ class SSHConnection:
logger
.
error
(
e
)
continue
return
sock
class
TelnetConnection
:
def
__init__
(
self
,
asset
,
system_user
,
client
):
self
.
client
=
client
self
.
asset
=
asset
self
.
system_user
=
system_user
self
.
sock
=
None
self
.
sel
=
selectors
.
DefaultSelector
()
self
.
incorrect_pattern
=
re
.
compile
(
r'incorrect|failed|失败'
,
re
.
I
)
self
.
username_pattern
=
re
.
compile
(
r'login:\s*$|username:\s*$|用户名:\s*$|账\s*号:\s*$'
,
re
.
I
)
self
.
password_pattern
=
re
.
compile
(
r'password:\s*$|passwd:\s*$|密\s*码:\s*$'
,
re
.
I
)
self
.
success_pattern
=
re
.
compile
(
r'Last\s*login|success|成功'
,
re
.
I
)
def
get_socket
(
self
):
logger
.
info
(
'Get telnet server socket. {}'
.
format
(
self
.
client
.
user
))
self
.
sock
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_STREAM
)
self
.
sock
.
connect
((
self
.
asset
.
ip
,
self
.
asset
.
port
))
# Send SGA and ECHO options to Telnet Server
self
.
sock
.
send
(
telnetlib
.
IAC
+
telnetlib
.
DO
+
telnetlib
.
SGA
)
self
.
sock
.
send
(
telnetlib
.
IAC
+
telnetlib
.
DO
+
telnetlib
.
ECHO
)
self
.
sel
.
register
(
self
.
sock
,
selectors
.
EVENT_READ
)
while
True
:
events
=
self
.
sel
.
select
()
for
sock
in
[
key
.
fileobj
for
key
,
_
in
events
]:
data
=
sock
.
recv
(
BUF_SIZE
)
if
sock
==
self
.
sock
:
logger
.
info
(
b
'[Telnet server send]: '
+
data
)
if
not
data
:
self
.
sock
.
close
()
msg
=
'The server <{}> closes the connection.'
.
format
(
self
.
asset
.
hostname
)
logger
.
info
(
msg
)
return
None
,
msg
if
data
.
startswith
(
telnetlib
.
IAC
):
self
.
option_negotiate
(
data
)
else
:
result
=
self
.
login_auth
(
data
)
if
result
:
msg
=
'Successful asset connection.<{}>/<{}>/<{}>.'
.
format
(
self
.
client
.
user
,
self
.
system_user
.
username
,
self
.
asset
.
hostname
)
logger
.
info
(
msg
)
self
.
client
.
send
(
b
'
\r\n
'
+
data
)
return
self
.
sock
,
None
elif
result
is
False
:
self
.
sock
.
close
()
msg
=
'Authentication failed.
\r\n
'
logger
.
info
(
msg
)
return
None
,
msg
elif
result
is
None
:
continue
def
option_negotiate
(
self
,
data
):
"""
Telnet server option negotiate before connection
:param data: option negotiate data
:return:
"""
logger
.
info
(
b
'[Server options negotiate]: '
+
data
)
data_list
=
data
.
split
(
telnetlib
.
IAC
)
new_data_list
=
[]
for
x
in
data_list
:
if
x
==
telnetlib
.
DO
+
telnetlib
.
ECHO
:
new_data_list
.
append
(
telnetlib
.
WONT
+
telnetlib
.
ECHO
)
elif
x
==
telnetlib
.
WILL
+
telnetlib
.
ECHO
:
new_data_list
.
append
(
telnetlib
.
DO
+
telnetlib
.
ECHO
)
elif
x
==
telnetlib
.
WILL
+
telnetlib
.
SGA
:
new_data_list
.
append
(
telnetlib
.
DO
+
telnetlib
.
SGA
)
elif
x
==
telnetlib
.
DO
+
telnetlib
.
TTYPE
:
new_data_list
.
append
(
telnetlib
.
WILL
+
telnetlib
.
TTYPE
)
elif
x
==
telnetlib
.
SB
+
telnetlib
.
TTYPE
+
b
'
\x01
'
:
new_data_list
.
append
(
telnetlib
.
SB
+
telnetlib
.
TTYPE
+
b
'
\x00
'
+
b
'XTERM-256COLOR'
)
elif
telnetlib
.
DO
in
x
:
new_data_list
.
append
(
x
.
replace
(
telnetlib
.
DO
,
telnetlib
.
WONT
))
elif
telnetlib
.
WILL
in
x
:
new_data_list
.
append
(
x
.
replace
(
telnetlib
.
WILL
,
telnetlib
.
DONT
))
elif
telnetlib
.
WONT
in
x
:
new_data_list
.
append
(
x
.
replace
(
telnetlib
.
WONT
,
telnetlib
.
DONT
))
elif
telnetlib
.
DONT
in
x
:
new_data_list
.
append
(
x
.
replace
(
telnetlib
.
DONT
,
telnetlib
.
WONT
))
else
:
new_data_list
.
append
(
x
)
new_data
=
telnetlib
.
IAC
.
join
(
new_data_list
)
logger
.
info
(
b
'[Client options negotiate]: '
+
new_data
)
self
.
sock
.
send
(
new_data
)
def
login_auth
(
self
,
raw_data
):
logger
.
info
(
'[Telnet login auth]: ({})'
.
format
(
self
.
client
.
user
))
try
:
data
=
raw_data
.
decode
(
'utf-8'
)
except
UnicodeDecodeError
:
try
:
data
=
raw_data
.
decode
(
'gbk'
)
except
UnicodeDecodeError
:
logger
.
info
(
b
'[Decode error]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
None
if
self
.
incorrect_pattern
.
search
(
data
):
logger
.
info
(
b
'[Login incorrect prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
False
elif
self
.
username_pattern
.
search
(
data
):
logger
.
info
(
b
'[Username prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
self
.
sock
.
send
(
self
.
system_user
.
username
.
encode
(
'utf-8'
)
+
b
'
\r\n
'
)
return
None
elif
self
.
password_pattern
.
search
(
data
):
logger
.
info
(
b
'[Password prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
self
.
sock
.
send
(
self
.
system_user
.
password
.
encode
(
'utf-8'
)
+
b
'
\r\n
'
)
return
None
elif
self
.
success_pattern
.
search
(
data
):
logger
.
info
(
b
'[Login Success prompt]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
True
else
:
logger
.
info
(
b
'[No match]: '
+
b
'>>'
+
raw_data
+
b
'<<'
)
return
None
coco/models.py
View file @
f46cd8cd
...
...
@@ -86,8 +86,178 @@ class Client:
# print("GC: Client object has been gc")
class
BaseServer
:
"""
Base Server
Achieve command record
sub-class: Server, Telnet Server
"""
def
__init__
(
self
):
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
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
@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
)
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
):
return
self
.
sock
.
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
):
logger
.
info
(
"Closed server {}"
.
format
(
self
))
self
.
parse
(
b
''
)
self
.
stop_evt
.
set
()
self
.
sock
.
close
()
def
__getattr__
(
self
,
item
):
return
getattr
(
self
.
sock
,
item
)
def
__str__
(
self
):
return
"<To: {}>"
.
format
(
str
(
self
.
asset
))
class
Server
(
BaseServer
):
"""
SSH 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.
"""
# Todo: Server name is not very suitable
def
__init__
(
self
,
chan
,
sock
,
asset
,
system_user
):
super
(
Server
,
self
)
.
__init__
()
self
.
chan
=
chan
self
.
sock
=
sock
self
.
asset
=
asset
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:
"""
SSH 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.
...
...
@@ -199,6 +369,7 @@ class Server:
# def __del__(self):
# print("GC: Server object has been gc")
'''
class
WSProxy
:
...
...
coco/proxy.py
View file @
f46cd8cd
...
...
@@ -8,8 +8,8 @@ import time
from
paramiko.ssh_exception
import
SSHException
from
.session
import
Session
from
.models
import
Server
from
.connection
import
SSHConnection
from
.models
import
Server
,
TelnetServer
from
.connection
import
SSHConnection
,
TelnetConnection
from
.ctx
import
current_app
,
app_service
from
.utils
import
wrap_with_line_feed
as
wr
,
wrap_with_warning
as
warning
,
\
get_logger
,
net_input
...
...
@@ -18,6 +18,8 @@ from .utils import wrap_with_line_feed as wr, wrap_with_warning as warning, \
logger
=
get_logger
(
__file__
)
TIMEOUT
=
10
BUF_SIZE
=
4096
MANUAL_LOGIN
=
'manual'
AUTO_LOGIN
=
'auto'
class
ProxyServer
:
...
...
@@ -34,13 +36,24 @@ class ProxyServer:
"""
password
,
private_key
=
\
app_service
.
get_system_user_auth_info
(
system_user
)
if
not
password
and
not
private_key
:
if
system_user
.
login_mode
==
MANUAL_LOGIN
or
(
not
password
and
not
private_key
)
:
prompt
=
"{}'s password: "
.
format
(
system_user
.
username
)
password
=
net_input
(
self
.
client
,
prompt
=
prompt
,
sensitive
=
True
)
system_user
.
password
=
password
system_user
.
private_key
=
private_key
def
proxy
(
self
,
asset
,
system_user
):
if
asset
.
protocol
!=
system_user
.
protocol
:
msg
=
'System user <{}> and asset <{}> protocol are inconsistent.'
.
format
(
system_user
.
name
,
asset
.
hostname
)
self
.
client
.
send
(
warning
(
wr
(
msg
,
before
=
1
,
after
=
0
)))
return
if
system_user
.
login_mode
==
MANUAL_LOGIN
or
not
system_user
.
username
:
system_user_name
=
net_input
(
self
.
client
,
prompt
=
'username: '
,
before
=
1
)
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
)
...
...
@@ -74,15 +87,26 @@ class ProxyServer:
if
not
self
.
validate_permission
(
asset
,
system_user
):
self
.
client
.
send
(
warning
(
'No permission'
))
return
None
if
True
:
if
system_user
.
protocol
==
asset
.
protocol
==
'telnet'
:
server
=
self
.
get_telnet_server_conn
(
asset
,
system_user
)
elif
system_user
.
protocol
==
asset
.
protocol
==
'ssh'
:
server
=
self
.
get_ssh_server_conn
(
asset
,
system_user
)
else
:
server
=
self
.
get_ssh_server_conn
(
asset
,
system_user
)
server
=
None
return
server
# Todo: Support telnet
def
get_telnet_server_conn
(
self
,
asset
,
system_user
):
pass
telnet
=
TelnetConnection
(
asset
,
system_user
,
self
.
client
)
sock
,
msg
=
telnet
.
get_socket
()
if
not
sock
:
self
.
client
.
send
(
warning
(
wr
(
msg
,
before
=
1
,
after
=
0
)))
server
=
None
else
:
server
=
TelnetServer
(
sock
,
asset
,
system_user
)
# self.client.send(b'\r\n')
self
.
connecting
=
False
return
server
def
get_ssh_server_conn
(
self
,
asset
,
system_user
):
request
=
self
.
client
.
request
...
...
@@ -116,6 +140,8 @@ class ProxyServer:
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
()
...
...
requirements/requirements.txt
View file @
f46cd8cd
...
...
@@ -19,7 +19,7 @@ itsdangerous==0.24
Jinja2==2.10
jmespath==0.9.3
jms-storage==0.0.18
jumpserver-python-sdk==0.0.4
2
jumpserver-python-sdk==0.0.4
3
MarkupSafe==1.0
oss2==2.4.0
paramiko==2.4.0
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment