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
92a50b56
Commit
92a50b56
authored
Oct 31, 2017
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Feature] 完成app注册sdk对接
parent
e9f6547d
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
85 additions
and
144 deletions
+85
-144
app.py
coco/app.py
+7
-6
auth.py
coco/auth.py
+32
-75
httpd.py
coco/httpd.py
+1
-1
sdk.py
coco/sdk.py
+42
-60
utils.py
coco/utils.py
+3
-2
No files found.
coco/app.py
View file @
92a50b56
...
...
@@ -5,10 +5,9 @@ import logging
from
.config
import
Config
from
.sshd
import
SSHServer
from
.
ws
import
WS
Server
from
.
httpd
import
Http
Server
from
.logging
import
create_logger
from
.sdk
import
AppService
from
.auth
import
AppAccessKey
__version__
=
'0.4.0'
...
...
@@ -27,6 +26,7 @@ class Coco:
'SSHD_PORT'
:
2222
,
'WS_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'
:
'INFO'
,
...
...
@@ -43,7 +43,6 @@ class Coco:
self
.
config
=
self
.
config_class
(
BASE_DIR
,
defaults
=
self
.
default_config
)
self
.
sessions
=
[]
self
.
clients
=
[]
self
.
ws
=
None
self
.
root_path
=
root_path
self
.
name
=
name
self
.
lock
=
threading
.
Lock
()
...
...
@@ -55,7 +54,7 @@ class Coco:
if
root_path
is
None
:
self
.
root_path
=
BASE_DIR
self
.
make_logger
()
self
.
httpd
=
None
self
.
sshd
=
None
self
.
running
=
True
...
...
@@ -63,8 +62,9 @@ class Coco:
create_logger
(
self
)
def
prepare
(
self
):
self
.
make_logger
()
self
.
sshd
=
SSHServer
(
self
)
self
.
ws
=
WS
Server
(
self
)
self
.
httpd
=
Http
Server
(
self
)
self
.
initial_service
()
def
heartbeat
(
self
):
...
...
@@ -94,7 +94,7 @@ class Coco:
thread
.
start
()
def
run_ws
(
self
):
thread
=
threading
.
Thread
(
target
=
self
.
ws
.
run
,
args
=
())
thread
=
threading
.
Thread
(
target
=
self
.
httpd
.
run
,
args
=
())
thread
.
daemon
=
True
thread
.
start
()
...
...
@@ -123,6 +123,7 @@ class Coco:
def
initial_service
(
self
):
self
.
service
=
AppService
(
self
)
self
.
service
.
initial
()
def
monitor_session
(
self
):
pass
...
...
coco/auth.py
View file @
92a50b56
...
...
@@ -10,19 +10,6 @@ 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
):
self
.
id
=
access_key
.
id
...
...
@@ -34,49 +21,6 @@ class AccessKeyAuth(object):
req
.
headers
[
'Authorization'
]
=
"Sign {0}:{1}"
.
format
(
self
.
id
,
signature
)
return
req
#
# class AccessTokenAuth(object):
# def __init__(self, token):
# self.token = token
#
# def sign_request(self, req):
# req.headers['Authorization'] = 'Bearer {0}'.format(self.token)
# return req
#
#
# class SessionAuth(object):
# def __init__(self, session_id, csrf_token):
# self.session_id = session_id
# self.csrf_token = csrf_token
#
# def sign_request(self, req):
# cookie = [v for v in req.headers.get('Cookie', '').split(';')
# if v.strip()]
# cookie.extend(['sessionid='+self.session_id,
# 'csrftoken='+self.csrf_token])
# req.headers['Cookie'] = ';'.join(cookie)
# req.headers['X-CSRFTOKEN'] = self.csrf_token
# return req
# class Auth(object):
# def __init__(self, token=None, access_key_id=None,
# access_key_secret=None,
# session_id=None, csrf_token=None):
#
# if token is not None:
# self.instance = AccessTokenAuth(token)
# elif access_key_id and access_key_secret:
# self.instance = AccessKeyAuth(access_key_id, access_key_secret)
# elif session_id and csrf_token:
# self.instance = SessionAuth(session_id, csrf_token)
# else:
# raise SyntaxError('Need token or access_key_id, access_key_secret '
# 'or session_id, csrf_token')
#
# def sign_request(self, req):
# return self.instance.sign_request(req)
class
AccessKey
(
object
):
def
__init__
(
self
,
id
=
None
,
secret
=
None
):
...
...
@@ -97,13 +41,13 @@ class AccessKey(object):
@classmethod
def
load_from_val
(
cls
,
val
,
**
kwargs
):
id
,
secret
=
cls
.
clean
(
val
,
**
kwargs
)
return
cls
(
id
,
secret
)
return
cls
(
id
=
id
,
secret
=
secret
)
@classmethod
def
load_from_env
(
cls
,
env
,
**
kwargs
):
value
=
os
.
environ
.
get
(
env
)
id
,
secret
=
cls
.
clean
(
value
,
**
kwargs
)
return
cls
(
id
,
secret
)
return
cls
(
id
=
id
,
secret
=
secret
)
@classmethod
def
load_from_f
(
cls
,
f
,
**
kwargs
):
...
...
@@ -117,7 +61,7 @@ class AccessKey(object):
break
f
.
close
()
id
,
secret
=
cls
.
clean
(
value
,
**
kwargs
)
return
cls
(
id
,
secret
)
return
cls
(
id
=
id
,
secret
=
secret
)
def
save_to_f
(
self
,
f
,
silent
=
False
):
if
isinstance
(
f
,
str
):
...
...
@@ -131,44 +75,57 @@ class AccessKey(object):
finally
:
f
.
close
()
def
__
nonzero
__
(
self
):
def
__
bool
__
(
self
):
return
bool
(
self
.
id
and
self
.
secret
)
__bool__
=
__nonzero__
def
__str__
(
self
):
return
'{0}:{1}'
.
format
(
self
.
id
,
self
.
secret
)
__repr__
=
__str__
def
__repr__
(
self
):
return
'{0}:{1}'
.
format
(
self
.
id
,
self
.
secret
)
class
AppAccessKey
(
AccessKey
):
"""使用Access key来认证"""
def
__init__
(
self
,
app
,
id
=
None
,
secret
=
None
):
def
__init__
(
self
,
id
=
None
,
secret
=
None
):
super
()
.
__init__
(
id
=
id
,
secret
=
secret
)
self
.
app
=
None
def
set_app
(
self
,
app
):
self
.
app
=
app
self
.
_key_store
=
app
.
config
[
'ACCESS_KEY_STORE'
]
self
.
_key_env
=
app
.
config
[
'ACCESS_KEY_ENV'
]
self
.
_key_val
=
app
.
config
[
'ACCESS_KEY'
]
@property
def
_key_env
(
self
):
return
self
.
app
.
config
[
'ACCESS_KEY_ENV'
]
@property
def
_key_val
(
self
):
return
self
.
app
.
config
[
'ACCESS_KEY'
]
@property
def
_key_file
(
self
):
return
self
.
app
.
config
[
'ACCESS_KEY_FILE'
]
def
load_from_conf_env
(
self
,
sep
=
':'
,
silent
=
False
):
return
super
()
.
load_from_env
(
self
.
_key_env
,
sep
=
sep
,
silent
=
silent
)
def
load_from_conf
(
self
,
sep
=
':'
,
silent
=
False
):
def
load_from_conf
_val
(
self
,
sep
=
':'
,
silent
=
False
):
return
super
()
.
load_from_val
(
self
.
_key_val
,
sep
=
sep
,
silent
=
silent
)
def
load_from_
key_stor
e
(
self
,
sep
=
':'
,
silent
=
False
):
return
super
()
.
load_from_f
(
self
.
_key_
stor
e
,
sep
=
sep
,
silent
=
silent
)
def
load_from_
conf_fil
e
(
self
,
sep
=
':'
,
silent
=
False
):
return
super
()
.
load_from_f
(
self
.
_key_
fil
e
,
sep
=
sep
,
silent
=
silent
)
def
load
(
self
,
**
kwargs
):
"""Should return access_key_id, access_key_secret"""
for
method
in
[
self
.
load_from_env
,
self
.
load_from_conf
,
self
.
load_from_
key_stor
e
]:
for
method
in
[
self
.
load_from_
conf_
env
,
self
.
load_from_conf
_val
,
self
.
load_from_
conf_fil
e
]:
try
:
return
method
(
**
kwargs
)
except
LoadAccessKeyError
:
continue
return
None
def
save_to_key_store
(
self
):
return
super
()
.
save_to_f
(
self
.
_key_store
)
\ No newline at end of file
def
save_to_file
(
self
):
return
super
()
.
save_to_f
(
self
.
_key_file
)
\ No newline at end of file
coco/
ws
.py
→
coco/
httpd
.py
View file @
92a50b56
...
...
@@ -72,7 +72,7 @@ class MonitorWehSocketHandler(BaseWehSocketHandler):
pass
class
WS
Server
:
class
Http
Server
:
routers
=
[
(
r'/ws/interactive/'
,
InteractiveWehSocketHandler
),
(
r'/ws/proxy/(?P<asset_id>[0-9]+)/(?P<system_user_id>[0-9]+)/'
,
ProxyWehSocketHandler
),
...
...
coco/sdk.py
View file @
92a50b56
...
...
@@ -80,7 +80,7 @@ class AppRequests(object):
@staticmethod
def
clean_result
(
resp
):
if
resp
.
status_code
>=
500
:
raise
ResponseError
(
"Response code is {0.code}: {0.text}"
.
format
(
resp
))
raise
ResponseError
(
"Response code is {0.
status_
code}: {0.text}"
.
format
(
resp
))
try
:
_
=
resp
.
json
()
...
...
@@ -138,82 +138,85 @@ class AppService:
def
__init__
(
self
,
app
):
self
.
app
=
app
self
.
access_key
=
None
self
.
requests
=
AppRequests
(
app
.
config
[
'JUMPSERVER_ENDPOINT'
])
self
.
prepare
()
self
.
requests
=
AppRequests
(
app
.
config
[
'CORE_HOST'
])
def
prepare
(
self
):
def
initial
(
self
):
self
.
load_access_key
()
self
.
set_auth
()
self
.
valid_auth
()
def
load_access_key
(
self
):
# Must be get access key if not register it
self
.
access_key
=
self
.
access_key_class
(
self
.
app
)
.
load
()
self
.
access_key
=
self
.
access_key_class
()
self
.
access_key
.
set_app
(
self
.
app
)
self
.
access_key
=
self
.
access_key
.
load
()
if
self
.
access_key
is
None
:
logger
.
info
(
"No access key found, register it"
)
self
.
register_and_save
()
def
set_auth
(
self
):
self
.
requests
.
auth
=
AccessKeyAuth
(
self
.
access_key
)
def
valid_auth
(
self
):
while
True
:
delay
=
1
delay
=
1
while
delay
<
300
:
if
self
.
heatbeat
()
is
None
:
msg
=
"Access key is not valid or need admin "
\
"accepted, waiting
%
d s"
%
delay
logger
.
info
(
msg
)
delay
+=
1
time
.
sleep
(
1
)
delay
+=
3
time
.
sleep
(
3
)
else
:
break
if
delay
>=
300
:
logger
.
info
(
"Start timeout"
)
sys
.
exit
()
def
register_and_save
(
self
):
self
.
register
()
self
.
save_access_key
()
def
save_access_key
(
self
):
self
.
access_key
.
save_to_
key_stor
e
()
self
.
access_key
.
save_to_
fil
e
()
def
register
(
self
):
try
:
resp
=
self
.
requests
.
post
(
'terminal-register'
,
data
=
{
'name'
:
self
.
app
.
name
},
use_auth
=
False
)
resp
=
self
.
requests
.
post
(
'terminal-register'
,
data
=
{
'name'
:
self
.
app
.
name
},
use_auth
=
False
)
except
(
RequestError
,
ResponseError
)
as
e
:
logger
.
error
(
e
)
sys
.
exit
()
return
if
resp
.
status_code
==
201
:
access_key
=
resp
.
json
()[
'access_key'
]
access_key_id
=
access_key
[
'id'
]
access_key_secret
=
access_key
[
'secret'
]
self
.
access_key
=
self
.
access_key_class
(
app
=
self
.
app
,
id
=
access_key_id
,
secret
=
access_key_secret
id
=
access_key_id
,
secret
=
access_key_secret
)
self
.
access_key
.
set_app
(
self
.
app
)
logger
.
info
(
'Register app success:
%
s'
%
access_key_id
,)
elif
resp
.
status_code
==
40
0
:
elif
resp
.
status_code
==
40
9
:
msg
=
'{} exist already, register failed'
.
format
(
self
.
app
.
name
)
logging
.
error
(
msg
)
sys
.
exit
()
else
:
logging
.
error
(
'Register terminal {} failed unknown
'
.
format
(
self
.
app
.
name
))
logging
.
error
(
'Register terminal {} failed unknown
: {}'
.
format
(
self
.
app
.
name
,
resp
.
json
()
))
sys
.
exit
()
def
wait_util_accept
(
self
):
while
True
:
if
self
.
heatbeat
()
is
None
:
time
.
sleep
(
1
)
else
:
break
def
heatbeat
(
self
):
"""和Jumpserver维持心跳, 当Terminal断线后,jumpserver可以知晓
Todo: Jumpserver发送的任务也随heatbeat返回, 并执行,如 断开某用户
"""
r
,
content
=
self
.
requests
.
post
(
'terminal-heatbeat'
,
use_auth
=
True
)
if
r
.
status_code
==
201
:
return
content
try
:
resp
=
self
.
requests
.
post
(
'terminal-heatbeat'
,
use_auth
=
True
)
except
(
ResponseError
,
RequestError
):
return
None
if
resp
.
status_code
==
201
:
return
True
else
:
return
None
...
...
@@ -224,9 +227,9 @@ class AppService:
'asset_id'
:
asset_id
,
'system_user_id'
:
system_user_id
,
}
r
,
content
=
self
.
get
(
'validate-user-asset-permission'
,
use_auth
=
True
,
params
=
params
)
r
,
content
=
self
.
requests
.
get
(
'validate-user-asset-permission'
,
use_auth
=
True
,
params
=
params
)
if
r
.
status_code
==
200
:
return
True
else
:
...
...
@@ -234,7 +237,7 @@ class AppService:
def
get_system_user_auth_info
(
self
,
system_user
):
"""获取系统用户的认证信息: 密码, ssh私钥"""
r
,
content
=
self
.
get
(
'system-user-auth-info'
,
pk
=
system_user
[
'id'
])
r
,
content
=
self
.
requests
.
get
(
'system-user-auth-info'
,
pk
=
system_user
[
'id'
])
if
r
.
status_code
==
200
:
password
=
content
[
'password'
]
or
''
private_key_string
=
content
[
'private_key'
]
or
''
...
...
@@ -275,7 +278,7 @@ class AppService:
data
[
'date_start'
]
=
timestamp_to_datetime_str
(
data
[
'date_start'
])
data
[
'is_failed'
]
=
1
if
data
.
get
(
'is_failed'
)
else
0
r
,
content
=
self
.
post
(
'send-proxy-log'
,
data
=
data
,
use_auth
=
True
)
r
,
content
=
self
.
requests
.
post
(
'send-proxy-log'
,
data
=
data
,
use_auth
=
True
)
if
r
.
status_code
!=
201
:
logging
.
warning
(
'Send proxy log failed:
%
s'
%
content
)
return
None
...
...
@@ -296,7 +299,7 @@ class AppService:
data
[
'is_failed'
]
=
1
if
data
.
get
(
'is_failed'
)
else
0
data
[
'is_finished'
]
=
1
proxy_log_id
=
data
.
get
(
'proxy_log_id'
)
or
0
r
,
content
=
self
.
patch
(
'finish-proxy-log'
,
pk
=
proxy_log_id
,
data
=
data
)
r
,
content
=
self
.
requests
.
patch
(
'finish-proxy-log'
,
pk
=
proxy_log_id
,
data
=
data
)
if
r
.
status_code
!=
200
:
logging
.
warning
(
'Finish proxy log failed:
%
s'
%
proxy_log_id
)
...
...
@@ -327,7 +330,7 @@ class AppService:
output
=
d
[
'output'
]
.
encode
(
'utf-8'
,
'ignore'
)
d
[
'output'
]
=
base64
.
b64encode
(
output
)
.
decode
(
"utf-8"
)
result
,
content
=
self
.
post
(
'send-command-log'
,
data
=
data
)
result
,
content
=
self
.
requests
.
post
(
'send-command-log'
,
data
=
data
)
if
result
.
status_code
!=
201
:
logging
.
warning
(
'Send command log failed:
%
s'
%
content
)
return
False
...
...
@@ -349,7 +352,7 @@ class AppService:
if
d
.
get
(
'output'
)
and
isinstance
(
d
[
'output'
],
str
):
d
[
'output'
]
=
d
[
'output'
]
.
encode
(
'utf-8'
)
d
[
'output'
]
=
base64
.
b64encode
(
d
[
'output'
])
result
,
content
=
self
.
post
(
'send-record-log'
,
data
=
data
)
result
,
content
=
self
.
requests
.
post
(
'send-record-log'
,
data
=
data
)
if
result
.
status_code
!=
201
:
logging
.
warning
(
'Send record log failed:
%
s'
%
content
)
return
False
...
...
@@ -371,27 +374,6 @@ class AppService:
# user = user_service.is_authenticated()
# return user
def
login
(
self
,
data
):
"""用户登录Terminal时需要向Jumpserver进行认证, 登陆成功后返回用户和token
data = {
'username': 'admin',
'password': 'admin',
'public_key': 'public key string',
'login_type': 'ST', # (('ST', 'SSH Terminal'),
# ('WT', 'Web Terminal'))
'remote_addr': '2.2.2.2', # User ip address not app address
}
"""
r
,
content
=
self
.
post
(
'user-auth'
,
data
=
data
,
use_auth
=
False
)
if
r
.
status_code
==
200
:
self
.
token
=
content
[
'token'
]
self
.
user
=
content
[
'user'
]
self
.
auth
(
self
.
token
)
return
self
.
user
,
self
.
token
else
:
return
None
,
None
@cached
(
TTLCache
(
maxsize
=
100
,
ttl
=
60
))
def
get_user_assets
(
self
,
user
):
"""获取用户被授权的资产列表
...
...
@@ -399,7 +381,7 @@ class AppService:
'system_users_granted': [{'id': 1, 'username': 'x',..}]
]
"""
r
,
content
=
self
.
get
(
'user-assets'
,
pk
=
user
[
'id'
],
use_auth
=
True
)
r
,
content
=
self
.
requests
.
get
(
'user-assets'
,
pk
=
user
[
'id'
],
use_auth
=
True
)
if
r
.
status_code
==
200
:
assets
=
content
else
:
...
...
@@ -416,7 +398,7 @@ class AppService:
"""获取用户授权的资产组列表
[{'name': 'x', 'comment': 'x', 'assets_amount': 2}, ..]
"""
r
,
content
=
self
.
get
(
'user-asset-groups'
,
pk
=
user
[
'id'
],
uassetsse_auth
=
True
)
r
,
content
=
self
.
requests
.
get
(
'user-asset-groups'
,
pk
=
user
[
'id'
],
uassetsse_auth
=
True
)
if
r
.
status_code
==
200
:
asset_groups
=
content
else
:
...
...
@@ -429,7 +411,7 @@ class AppService:
"""获取用户授权的资产组列表及下面的资产
[{'name': 'x', 'comment': 'x', 'assets': []}, ..]
"""
r
,
content
=
self
.
get
(
'user-asset-groups-assets'
,
pk
=
user
[
'id'
],
use_auth
=
True
)
r
,
content
=
self
.
requests
.
get
(
'user-asset-groups-assets'
,
pk
=
user
[
'id'
],
use_auth
=
True
)
if
r
.
status_code
==
200
:
asset_groups_assets
=
content
else
:
...
...
@@ -443,7 +425,7 @@ class AppService:
:param asset_group_id: 资产组id
"""
r
,
content
=
self
.
get
(
'assets-of-group'
,
use_auth
=
True
,
r
,
content
=
self
.
requests
.
get
(
'assets-of-group'
,
use_auth
=
True
,
pk
=
asset_group_id
)
if
r
.
status_code
==
200
:
assets
=
content
...
...
coco/utils.py
View file @
92a50b56
...
...
@@ -91,7 +91,8 @@ def content_md5(data):
"""
if
isinstance
(
data
,
str
):
data
=
hashlib
.
md5
(
data
.
encode
(
'utf-8'
))
return
base64
.
b64encode
(
data
.
digest
())
value
=
base64
.
b64encode
(
data
.
digest
())
return
value
.
decode
(
'utf-8'
)
_STRPTIME_LOCK
=
threading
.
Lock
()
...
...
@@ -126,7 +127,7 @@ def iso8601_to_unixtime(time_string):
def
make_signature
(
access_key_secret
,
date
=
None
):
if
isinstance
(
date
,
bytes
):
date
=
date
.
decode
(
"utf-8"
)
date
=
bytes
.
decode
(
date
)
if
isinstance
(
date
,
int
):
date_gmt
=
http_date
(
date
)
elif
date
is
None
:
...
...
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