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
83c0ed36
Commit
83c0ed36
authored
7 years ago
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update sdk
parent
d9d0c4ba
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
149 additions
and
160 deletions
+149
-160
app.py
coco/app.py
+7
-0
auth.py
coco/auth.py
+33
-15
config.py
coco/config.py
+1
-17
exception.py
coco/exception.py
+3
-0
forward.py
coco/forward.py
+1
-1
interactive.py
coco/interactive.py
+1
-1
sdk.py
coco/sdk.py
+100
-94
sshd.py
coco/sshd.py
+1
-1
utils.py
coco/utils.py
+2
-31
No files found.
coco/app.py
View file @
83c0ed36
...
...
@@ -7,6 +7,8 @@ from .config import Config
from
.sshd
import
SSHServer
from
.ws
import
WSServer
from
.logging
import
create_logger
from
.sdk
import
AppService
from
.auth
import
AppAccessKey
__version__
=
'0.4.0'
...
...
@@ -46,6 +48,7 @@ class Coco:
self
.
name
=
name
self
.
lock
=
threading
.
Lock
()
self
.
stop_evt
=
threading
.
Event
()
self
.
service
=
None
if
name
is
None
:
self
.
name
=
self
.
config
[
'NAME'
]
...
...
@@ -62,6 +65,7 @@ class Coco:
def
prepare
(
self
):
self
.
sshd
=
SSHServer
(
self
)
self
.
ws
=
WSServer
(
self
)
self
.
initial_service
()
def
heartbeat
(
self
):
pass
...
...
@@ -117,6 +121,9 @@ class Coco:
except
:
pass
def
initial_service
(
self
):
self
.
service
=
AppService
(
self
)
def
monitor_session
(
self
):
pass
This diff is collapsed.
Click to expand it.
coco/auth.py
View file @
83c0ed36
...
...
@@ -3,14 +3,26 @@
#
import
os
import
six
import
logging
from
io
import
IOBas
e
import
tim
e
from
.
import
utils
from
.exception
import
LoadAccessKeyError
def
make_signature
(
access_key_secret
,
date
=
None
):
if
isinstance
(
date
,
bytes
):
date
=
date
.
decode
(
"utf-8"
)
if
isinstance
(
date
,
int
):
date_gmt
=
utils
.
http_date
(
date
)
elif
date
is
None
:
date_gmt
=
utils
.
http_date
(
int
(
time
.
time
()))
else
:
date_gmt
=
date
data
=
str
(
access_key_secret
)
+
"
\n
"
+
date_gmt
return
utils
.
content_md5
(
data
)
class
AccessKeyAuth
(
object
):
def
__init__
(
self
,
access_key_id
,
access_key_secret
):
self
.
id
=
access_key_id
...
...
@@ -48,7 +60,8 @@ class SessionAuth(object):
class
Auth
(
object
):
def
__init__
(
self
,
token
=
None
,
access_key_id
=
None
,
access_key_secret
=
None
,
def
__init__
(
self
,
token
=
None
,
access_key_id
=
None
,
access_key_secret
=
None
,
session_id
=
None
,
csrf_token
=
None
):
if
token
is
not
None
:
...
...
@@ -58,7 +71,7 @@ class Auth(object):
elif
session_id
and
csrf_token
:
self
.
instance
=
SessionAuth
(
session_id
,
csrf_token
)
else
:
raise
OS
Error
(
'Need token or access_key_id, access_key_secret '
raise
Syntax
Error
(
'Need token or access_key_id, access_key_secret '
'or session_id, csrf_token'
)
def
sign_request
(
self
,
req
):
...
...
@@ -70,22 +83,27 @@ class AccessKey(object):
self
.
id
=
id
self
.
secret
=
secret
def
clean
(
self
,
value
,
delimiter
=
':'
,
silent
=
False
):
@staticmethod
def
clean
(
value
,
delimiter
=
':'
,
silent
=
False
):
try
:
self
.
id
,
self
.
secret
=
value
.
split
(
delimiter
)
id
,
secret
=
value
.
split
(
delimiter
)
except
(
AttributeError
,
ValueError
)
as
e
:
if
not
silent
:
raise
LoadAccessKeyError
(
e
)
return
''
,
''
else
:
return
':'
.
join
([
self
.
id
,
self
.
secret
])
return
id
,
secret
def
load_from_env
(
self
,
env
,
delimiter
=
':'
,
silent
=
False
):
@classmethod
def
load_from_env
(
cls
,
env
,
**
kwargs
):
value
=
os
.
environ
.
get
(
env
)
return
self
.
clean
(
value
,
delimiter
,
silent
)
id
,
secret
=
cls
.
clean
(
value
,
**
kwargs
)
return
cls
(
id
,
secret
)
def
load_from_f
(
self
,
f
,
delimiter
=
':'
,
silent
=
False
):
@classmethod
def
load_from_f
(
cls
,
f
,
**
kwargs
):
value
=
''
if
isinstance
(
f
,
s
ix
.
string_types
)
and
os
.
path
.
isfile
(
f
):
if
isinstance
(
f
,
s
tr
)
and
os
.
path
.
isfile
(
f
):
f
=
open
(
f
)
if
hasattr
(
f
,
'read'
):
for
line
in
f
:
...
...
@@ -93,10 +111,11 @@ class AccessKey(object):
value
=
line
.
strip
()
break
f
.
close
()
return
self
.
clean
(
value
,
delimiter
,
silent
)
id
,
secret
=
cls
.
clean
(
value
,
**
kwargs
)
return
cls
(
id
,
secret
)
def
save_to_f
(
self
,
f
,
silent
=
False
):
if
isinstance
(
f
,
s
ix
.
string_types
):
if
isinstance
(
f
,
s
tr
):
f
=
open
(
f
,
'w'
)
try
:
f
.
write
(
str
(
'{0}:{1}'
.
format
(
self
.
id
,
self
.
secret
)))
...
...
@@ -113,7 +132,6 @@ class AccessKey(object):
def
__str__
(
self
):
return
'{0}:{1}'
.
format
(
self
.
id
,
self
.
secret
)
__repr__
=
__str__
...
...
@@ -133,7 +151,7 @@ class ServiceAccessKey(AccessKey):
default_key_store
=
os
.
path
.
join
(
os
.
environ
.
get
(
'HOME'
,
''
),
'.access_key'
)
def
__init__
(
self
,
id
=
None
,
secret
=
None
,
config
=
None
):
super
(
ServiceAccessKey
,
self
)
.
__init__
(
id
=
id
,
secret
=
secret
)
super
()
.
__init__
(
id
=
id
,
secret
=
secret
)
self
.
config
=
config
or
{}
self
.
_key_store
=
None
self
.
_key_env
=
None
...
...
This diff is collapsed.
Click to expand it.
coco/config.py
View file @
83c0ed36
...
...
@@ -263,20 +263,4 @@ class Config(dict):
return
'<
%
s
%
s>'
%
(
self
.
__class__
.
__name__
,
dict
.
__repr__
(
self
))
API_URL_MAPPING
=
{
'terminal-register'
:
'/api/applications/v1/terminal/register/'
,
'terminal-heatbeat'
:
'/api/applications/v1/terminal/heatbeat/'
,
'send-proxy-log'
:
'/api/audits/v1/proxy-log/receive/'
,
'finish-proxy-log'
:
'/api/audits/v1/proxy-log/
%
s/'
,
'send-command-log'
:
'/api/audits/v1/command-log/'
,
'send-record-log'
:
'/api/audits/v1/record-log/'
,
'user-auth'
:
'/api/users/v1/auth/'
,
'user-assets'
:
'/api/perms/v1/user/
%
s/assets/'
,
'user-asset-groups'
:
'/api/perms/v1/user/
%
s/asset-groups/'
,
'user-asset-groups-assets'
:
'/api/perms/v1/user/my/asset-groups-assets/'
,
'assets-of-group'
:
'/api/perms/v1/user/my/asset-group/
%
s/assets/'
,
'my-profile'
:
'/api/users/v1/profile/'
,
'system-user-auth-info'
:
'/api/assets/v1/system-user/
%
s/auth-info/'
,
'validate-user-asset-permission'
:
'/api/perms/v1/asset-permission/user/validate/'
,
}
This diff is collapsed.
Click to expand it.
coco/exception.py
View file @
83c0ed36
...
...
@@ -12,3 +12,6 @@ class LoadAccessKeyError(Exception):
class
RequestError
(
Exception
):
pass
class
ResponseError
(
Exception
):
pass
This diff is collapsed.
Click to expand it.
coco/forward.py
View file @
83c0ed36
...
...
@@ -19,7 +19,7 @@ class ProxyServer:
def
__init__
(
self
,
app
,
client
):
self
.
app
=
app
self
.
client
=
client
self
.
request
=
client
.
request
self
.
request
=
client
.
do
self
.
server
=
None
self
.
connecting
=
True
...
...
This diff is collapsed.
Click to expand it.
coco/interactive.py
View file @
83c0ed36
...
...
@@ -18,7 +18,7 @@ class InteractiveServer:
def
__init__
(
self
,
app
,
client
):
self
.
app
=
app
self
.
client
=
client
self
.
request
=
client
.
request
self
.
request
=
client
.
do
def
display_banner
(
self
):
self
.
client
.
send
(
char
.
CLEAR_CHAR
)
...
...
This diff is collapsed.
Click to expand it.
coco/sdk.py
View file @
83c0ed36
...
...
@@ -11,15 +11,32 @@ import requests
from
requests.structures
import
CaseInsensitiveDict
from
cachetools
import
cached
,
TTLCache
from
.auth
import
Auth
,
ServiceAccessKey
from
.utils
import
sort_assets
,
PKey
,
to_dotmap
,
timestamp_to_datetime_str
from
.exception
import
RequestError
,
LoadAccessKeyError
from
.config
import
API_URL_MAPPING
from
.auth
import
Auth
,
ServiceAccessKey
,
AccessKey
from
.utils
import
sort_assets
,
PKey
,
timestamp_to_datetime_str
from
.exception
import
RequestError
,
LoadAccessKeyError
,
ResponseError
_USER_AGENT
=
'jms-sdk-py'
CACHED_TTL
=
os
.
environ
.
get
(
'CACHED_TTL'
,
30
)
API_URL_MAPPING
=
{
'terminal-register'
:
'/api/applications/v1/terminal/register/'
,
'terminal-heatbeat'
:
'/api/applications/v1/terminal/heatbeat/'
,
'send-proxy-log'
:
'/api/audits/v1/proxy-log/receive/'
,
'finish-proxy-log'
:
'/api/audits/v1/proxy-log/
%
s/'
,
'send-command-log'
:
'/api/audits/v1/command-log/'
,
'send-record-log'
:
'/api/audits/v1/record-log/'
,
'user-auth'
:
'/api/users/v1/auth/'
,
'user-assets'
:
'/api/perms/v1/user/
%
s/assets/'
,
'user-asset-groups'
:
'/api/perms/v1/user/
%
s/asset-groups/'
,
'user-asset-groups-assets'
:
'/api/perms/v1/user/my/asset-groups-assets/'
,
'assets-of-group'
:
'/api/perms/v1/user/my/asset-group/
%
s/assets/'
,
'my-profile'
:
'/api/users/v1/profile/'
,
'system-user-auth-info'
:
'/api/assets/v1/system-user/
%
s/auth-info/'
,
'validate-user-asset-permission'
:
'/api/perms/v1/asset-permission/user/validate/'
,
}
class
FakeResponse
(
object
):
def
__init__
(
self
):
...
...
@@ -31,118 +48,96 @@ class FakeResponse(object):
class
Request
(
object
):
func_mapping
=
{
methods
=
{
'get'
:
requests
.
get
,
'post'
:
requests
.
post
,
'patch'
:
requests
.
patch
,
'put'
:
requests
.
put
,
}
def
__init__
(
self
,
url
,
method
=
'get'
,
data
=
None
,
params
=
None
,
headers
=
None
,
content_type
=
'application/json'
,
app_name
=
'
'
):
def
__init__
(
self
,
url
,
method
=
'get'
,
data
=
None
,
params
=
None
,
headers
=
None
,
content_type
=
'application/json
'
):
self
.
url
=
url
self
.
method
=
method
self
.
params
=
params
or
{}
self
.
result
=
None
if
not
isinstance
(
headers
,
dict
):
headers
=
{}
self
.
headers
=
CaseInsensitiveDict
(
headers
)
self
.
headers
[
'Content-Type'
]
=
content_type
if
data
is
None
:
data
=
{}
self
.
data
=
json
.
dumps
(
data
)
if
'User-Agent'
not
in
self
.
headers
:
if
app_name
:
self
.
headers
[
'User-Agent'
]
=
_USER_AGENT
+
'/'
+
app_name
else
:
self
.
headers
[
'User-Agent'
]
=
_USER_AGENT
def
request
(
self
):
self
.
result
=
self
.
func_mapping
.
get
(
self
.
method
)(
def
do
(
self
):
result
=
self
.
methods
.
get
(
self
.
method
)(
url
=
self
.
url
,
headers
=
self
.
headers
,
data
=
self
.
data
,
params
=
self
.
params
)
print
(
self
.
headers
)
return
self
.
result
data
=
self
.
data
,
params
=
self
.
params
)
return
result
class
ApiRequest
(
object
):
api_url_mapping
=
API_URL_MAPPING
class
AppRequest
(
object
):
def
__init__
(
self
,
app_name
,
endpoint
,
auth
=
None
):
self
.
app_name
=
app_name
def
__init__
(
self
,
endpoint
,
auth
=
None
):
self
.
_auth
=
auth
self
.
req
=
None
self
.
endpoint
=
endpoint
@staticmethod
def
parse_result
(
result
):
def
clean_result
(
resp
):
if
resp
.
status_code
>=
400
:
return
ResponseError
(
"Response code is {0.code}: {0.text}"
.
format
(
resp
))
try
:
content
=
result
.
json
()
except
ValueError
:
content
=
{
'error'
:
'We only support json response'
}
logging
.
warning
(
result
.
content
)
logging
.
warning
(
content
)
except
AttributeError
:
content
=
{
'error'
:
'Request error'
}
return
result
,
content
def
request
(
self
,
api_name
=
None
,
pk
=
None
,
method
=
'get'
,
use_auth
=
True
,
result
=
resp
.
json
()
except
json
.
JSONDecodeError
:
return
RequestError
(
"Response json couldn't be decode: {0.text}"
.
format
(
resp
))
else
:
return
result
def
do
(
self
,
api_name
=
None
,
pk
=
None
,
method
=
'get'
,
use_auth
=
True
,
data
=
None
,
params
=
None
,
content_type
=
'application/json'
):
if
api_name
in
self
.
api_url_mapping
:
path
=
self
.
api_url_mapping
.
get
(
api_name
)
if
api_name
in
API_URL_MAPPING
:
path
=
API_URL_MAPPING
.
get
(
api_name
)
if
pk
and
'
%
s'
in
path
:
path
=
path
%
pk
else
:
path
=
'/'
url
=
self
.
endpoint
.
rstrip
(
'/'
)
+
path
print
(
url
)
self
.
req
=
req
=
Request
(
url
,
method
=
method
,
data
=
data
,
params
=
params
,
content_type
=
content_type
,
app_name
=
self
.
app_name
)
req
=
Request
(
url
,
method
=
method
,
data
=
data
,
params
=
params
,
content_type
=
content_type
)
if
use_auth
:
if
not
self
.
_auth
:
raise
RequestError
(
'Authentication required'
)
else
:
self
.
_auth
.
sign_request
(
req
)
try
:
result
=
req
.
request
()
if
result
.
status_code
>
500
:
logging
.
warning
(
'Server internal error'
)
except
(
requests
.
ConnectionError
,
requests
.
ConnectTimeout
):
result
=
FakeResponse
()
logging
.
warning
(
'Connect endpoint: {} error'
.
format
(
self
.
endpoint
))
return
self
.
parse_result
(
result
)
resp
=
req
.
do
()
except
(
requests
.
ConnectionError
,
requests
.
ConnectTimeout
)
as
e
:
return
RequestError
(
"Connect endpoint: {} {}"
.
format
(
self
.
endpoint
,
e
))
return
self
.
clean_result
(
resp
)
def
get
(
self
,
*
args
,
**
kwargs
):
kwargs
[
'method'
]
=
'get'
print
(
"+"
*
10
)
print
(
*
args
)
print
(
"+"
*
10
)
# print(**kwargs)
print
(
"+"
*
10
)
return
self
.
request
(
*
args
,
**
kwargs
)
return
self
.
do
(
*
args
,
**
kwargs
)
def
post
(
self
,
*
args
,
**
kwargs
):
kwargs
[
'method'
]
=
'post'
return
self
.
request
(
*
args
,
**
kwargs
)
return
self
.
do
(
*
args
,
**
kwargs
)
def
put
(
self
,
*
args
,
**
kwargs
):
kwargs
[
'method'
]
=
'put'
return
self
.
request
(
*
args
,
**
kwargs
)
return
self
.
do
(
*
args
,
**
kwargs
)
def
patch
(
self
,
*
args
,
**
kwargs
):
kwargs
[
'method'
]
=
'patch'
return
self
.
request
(
*
args
,
**
kwargs
)
return
self
.
do
(
*
args
,
**
kwargs
)
class
AppService
(
ApiRequest
)
:
class
AppService
:
"""使用该类和Jumpserver api进行通信,将terminal用到的常用api进行了封装,
直接调用方法即可.
from jms import AppService
...
...
@@ -172,14 +167,50 @@ class AppService(ApiRequest):
"""
access_key_class
=
ServiceAccessKey
def
__init__
(
self
,
app_name
,
endpoint
,
auth
=
None
,
config
=
None
):
super
(
AppService
,
self
)
.
__init__
(
app_name
,
endpoint
,
auth
=
auth
)
self
.
config
=
config
self
.
access_key
=
self
.
access_key_class
(
config
=
config
)
self
.
user
=
None
self
.
token
=
None
self
.
session_id
=
None
self
.
csrf_token
=
None
def
__init__
(
self
,
app
):
self
.
app
=
app
# super(AppService, self).__init__(app_name, endpoint, auth=auth)
# self.config = config
# self.access_key = self.access_key_class(config=config)
self
.
access_key
=
None
def
load_access_key
(
self
):
# Must be get access key if not register it
self
.
access_key
=
ServiceAccessKey
(
self
)
.
load
()
if
self
.
access_key
is
None
:
self
.
register_and_wait_for_accept
()
self
.
save_key_to_store
()
def
register_and_wait_for_accept
(
self
):
"""注册Terminal, 通常第一次启动需要向Jumpserver注册
content: {
'terminal': {'id': 1, 'name': 'terminal name', ...},
'user': {
'username': 'same as terminal name',
'name': 'same as username',
},
'access_key_id': 'ACCESS KEY ID',
'access_key_secret': 'ACCESS KEY SECRET',
}
"""
r
,
content
=
self
.
post
(
'terminal-register'
,
data
=
{
'name'
:
self
.
app
.
name
},
use_auth
=
False
)
if
r
.
status_code
==
201
:
logging
.
info
(
'Your can save access_key:
%
s somewhere '
'or set it in config'
%
content
[
'access_key_id'
])
return
True
,
to_dotmap
(
content
)
elif
r
.
status_code
==
200
:
logging
.
error
(
'Terminal {} exist already, register failed'
.
format
(
self
.
app_name
))
else
:
logging
.
error
(
'Register terminal {} failed'
.
format
(
self
.
app_name
))
return
False
,
None
def
save_key_so_store
(
self
):
pass
def
auth
(
self
,
access_key_id
=
None
,
access_key_secret
=
None
):
"""App认证, 请求api需要签名header
...
...
@@ -205,32 +236,7 @@ class AppService(ApiRequest):
else
:
raise
LoadAccessKeyError
(
'Load access key all failed, auth ignore'
)
def
register_terminal
(
self
):
"""注册Terminal, 通常第一次启动需要向Jumpserver注册
content: {
'terminal': {'id': 1, 'name': 'terminal name', ...},
'user': {
'username': 'same as terminal name',
'name': 'same as username',
},
'access_key_id': 'ACCESS KEY ID',
'access_key_secret': 'ACCESS KEY SECRET',
}
"""
r
,
content
=
self
.
post
(
'terminal-register'
,
data
=
{
'name'
:
self
.
app_name
},
use_auth
=
False
)
if
r
.
status_code
==
201
:
logging
.
info
(
'Your can save access_key:
%
s somewhere '
'or set it in config'
%
content
[
'access_key_id'
])
return
True
,
to_dotmap
(
content
)
elif
r
.
status_code
==
200
:
logging
.
error
(
'Terminal {} exist already, register failed'
.
format
(
self
.
app_name
))
else
:
logging
.
error
(
'Register terminal {} failed'
.
format
(
self
.
app_name
))
return
False
,
None
def
terminal_heatbeat
(
self
):
"""和Jumpserver维持心跳, 当Terminal断线后,jumpserver可以知晓
...
...
This diff is collapsed.
Click to expand it.
coco/sshd.py
View file @
83c0ed36
...
...
@@ -86,7 +86,7 @@ class SSHServer:
self
.
dispatch
(
client
)
def
dispatch
(
self
,
client
):
request_type
=
client
.
request
.
type
request_type
=
client
.
do
.
type
if
request_type
==
'pty'
:
InteractiveServer
(
self
.
app
,
client
)
.
activate
()
elif
request_type
==
'exec'
:
...
...
This diff is collapsed.
Click to expand it.
coco/utils.py
View file @
83c0ed36
...
...
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
import
hashlib
import
re
import
os
import
threading
import
base64
import
calendar
...
...
@@ -11,12 +12,11 @@ import time
import
datetime
from
io
import
StringIO
import
paramiko
import
pyte
import
pytz
from
email.utils
import
formatdate
import
paramiko
from
dotmap
import
DotMap
try
:
...
...
@@ -24,9 +24,6 @@ try:
except
ImportError
:
from
queue
import
Queue
,
Empty
from
.compat
import
to_string
,
to_bytes
def
ssh_key_string_to_obj
(
text
):
key_f
=
StringIO
(
text
)
...
...
@@ -258,21 +255,6 @@ def b64encode_as_string(data):
return
to_string
(
base64
.
b64encode
(
data
))
def
make_signature
(
access_key_secret
,
date
=
None
):
if
isinstance
(
date
,
bytes
):
date
=
date
.
decode
(
"utf-8"
)
if
isinstance
(
date
,
int
):
date_gmt
=
http_date
(
date
)
elif
date
is
None
:
date_gmt
=
http_date
(
int
(
time
.
time
()))
else
:
date_gmt
=
date
data
=
str
(
access_key_secret
)
+
"
\n
"
+
date_gmt
return
content_md5
(
data
)
def
split_string_int
(
s
):
"""Split string or int
...
...
@@ -335,17 +317,6 @@ def timestamp_to_datetime_str(ts):
return
dt
.
strftime
(
datetime_format
)
def
to_dotmap
(
data
):
"""将接受dict转换为DotMap"""
if
isinstance
(
data
,
dict
):
data
=
DotMap
(
data
)
elif
isinstance
(
data
,
list
):
data
=
[
DotMap
(
d
)
for
d
in
data
]
else
:
raise
ValueError
(
'Dict or list type required...'
)
return
data
class
MultiQueue
(
Queue
):
def
mget
(
self
,
size
=
1
,
block
=
True
,
timeout
=
5
):
items
=
[]
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment