Commit e9f6547d authored by ibuler's avatar ibuler

修改 auth 和sdk

parent 83c0ed36
...@@ -24,9 +24,9 @@ def make_signature(access_key_secret, date=None): ...@@ -24,9 +24,9 @@ def make_signature(access_key_secret, date=None):
class AccessKeyAuth(object): class AccessKeyAuth(object):
def __init__(self, access_key_id, access_key_secret): def __init__(self, access_key):
self.id = access_key_id self.id = access_key.id
self.secret = access_key_secret self.secret = access_key.secret
def sign_request(self, req): def sign_request(self, req):
req.headers['Date'] = utils.http_date() req.headers['Date'] = utils.http_date()
...@@ -34,48 +34,48 @@ class AccessKeyAuth(object): ...@@ -34,48 +34,48 @@ class AccessKeyAuth(object):
req.headers['Authorization'] = "Sign {0}:{1}".format(self.id, signature) req.headers['Authorization'] = "Sign {0}:{1}".format(self.id, signature)
return req return req
#
class AccessTokenAuth(object): # class AccessTokenAuth(object):
def __init__(self, token): # def __init__(self, token):
self.token = token # self.token = token
#
def sign_request(self, req): # def sign_request(self, req):
req.headers['Authorization'] = 'Bearer {0}'.format(self.token) # req.headers['Authorization'] = 'Bearer {0}'.format(self.token)
return req # return req
#
#
class SessionAuth(object): # class SessionAuth(object):
def __init__(self, session_id, csrf_token): # def __init__(self, session_id, csrf_token):
self.session_id = session_id # self.session_id = session_id
self.csrf_token = csrf_token # self.csrf_token = csrf_token
#
def sign_request(self, req): # def sign_request(self, req):
cookie = [v for v in req.headers.get('Cookie', '').split(';') # cookie = [v for v in req.headers.get('Cookie', '').split(';')
if v.strip()] # if v.strip()]
cookie.extend(['sessionid='+self.session_id, # cookie.extend(['sessionid='+self.session_id,
'csrftoken='+self.csrf_token]) # 'csrftoken='+self.csrf_token])
req.headers['Cookie'] = ';'.join(cookie) # req.headers['Cookie'] = ';'.join(cookie)
req.headers['X-CSRFTOKEN'] = self.csrf_token # req.headers['X-CSRFTOKEN'] = self.csrf_token
return req # return req
class Auth(object): # class Auth(object):
def __init__(self, token=None, access_key_id=None, # def __init__(self, token=None, access_key_id=None,
access_key_secret=None, # access_key_secret=None,
session_id=None, csrf_token=None): # session_id=None, csrf_token=None):
#
if token is not None: # if token is not None:
self.instance = AccessTokenAuth(token) # self.instance = AccessTokenAuth(token)
elif access_key_id and access_key_secret: # elif access_key_id and access_key_secret:
self.instance = AccessKeyAuth(access_key_id, access_key_secret) # self.instance = AccessKeyAuth(access_key_id, access_key_secret)
elif session_id and csrf_token: # elif session_id and csrf_token:
self.instance = SessionAuth(session_id, csrf_token) # self.instance = SessionAuth(session_id, csrf_token)
else: # else:
raise SyntaxError('Need token or access_key_id, access_key_secret ' # raise SyntaxError('Need token or access_key_id, access_key_secret '
'or session_id, csrf_token') # 'or session_id, csrf_token')
#
def sign_request(self, req): # def sign_request(self, req):
return self.instance.sign_request(req) # return self.instance.sign_request(req)
class AccessKey(object): class AccessKey(object):
...@@ -84,9 +84,9 @@ class AccessKey(object): ...@@ -84,9 +84,9 @@ class AccessKey(object):
self.secret = secret self.secret = secret
@staticmethod @staticmethod
def clean(value, delimiter=':', silent=False): def clean(value, sep=':', silent=False):
try: try:
id, secret = value.split(delimiter) id, secret = value.split(sep)
except (AttributeError, ValueError) as e: except (AttributeError, ValueError) as e:
if not silent: if not silent:
raise LoadAccessKeyError(e) raise LoadAccessKeyError(e)
...@@ -94,6 +94,11 @@ class AccessKey(object): ...@@ -94,6 +94,11 @@ class AccessKey(object):
else: else:
return id, secret return id, secret
@classmethod
def load_from_val(cls, val, **kwargs):
id, secret = cls.clean(val, **kwargs)
return cls(id, secret)
@classmethod @classmethod
def load_from_env(cls, env, **kwargs): def load_from_env(cls, env, **kwargs):
value = os.environ.get(env) value = os.environ.get(env)
...@@ -135,88 +140,35 @@ class AccessKey(object): ...@@ -135,88 +140,35 @@ class AccessKey(object):
__repr__ = __str__ __repr__ = __str__
class ServiceAccessKey(AccessKey): class AppAccessKey(AccessKey):
"""使用Access key来认证""" """使用Access key来认证"""
# 默认从配置文件中读取的设置 def __init__(self, app, id=None, secret=None):
# 配置文件中ACCESS_KEY值的名称
conf_attr_var = 'ACCESS_KEY'
# 配置文件中配置环境变量的名称
conf_env_var = 'ACCESS_KEY_ENV'
# 配置文件中定义Access key store的位置
conf_store_var = 'ACCESS_KEY_STORE'
# 如果用户配置中没有设置, 方法中也没填入, 使用下面默认
default_key_env = 'ACCESS_KEY_ENV'
default_key_store = os.path.join(os.environ.get('HOME', ''), '.access_key')
def __init__(self, id=None, secret=None, config=None):
super().__init__(id=id, secret=secret) super().__init__(id=id, secret=secret)
self.config = config or {} self.app = app
self._key_store = None self._key_store = app.config['ACCESS_KEY_STORE']
self._key_env = None self._key_env = app.config['ACCESS_KEY_ENV']
self._key_val = app.config['ACCESS_KEY']
# 获取key store位置
@property
def key_store(self):
if self._key_store:
return self._key_store
elif self.conf_store_var in self.config:
return self.config[self.conf_store_var]
else:
return self.default_key_store
@key_store.setter
def key_store(self, value):
self._key_store = value
# 获取access key的环境变量名
@property
def key_env(self):
if self._key_env:
return self._key_env
elif self.conf_env_var in self.config:
return self.config[self.conf_env_var]
else:
return self.default_key_env
@key_env.setter def load_from_conf_env(self, sep=':', silent=False):
def key_env(self, value): return super().load_from_env(self._key_env, sep=sep, silent=silent)
self._key_env = value
def load_from_conf_env(self, env=None, delimiter=':'): def load_from_conf(self, sep=':', silent=False):
if env is None: return super().load_from_val(self._key_val, sep=sep, silent=silent)
env = self.key_env
return super(ServiceAccessKey, self).\
load_from_env(env, delimiter=delimiter)
def load_from_conf_setting(self, attr=None, delimiter=':', silent=False): def load_from_key_store(self, sep=':', silent=False):
value = '' return super().load_from_f(self._key_store, sep=sep, silent=silent)
if attr is None:
attr = self.conf_attr_var def load(self, **kwargs):
if attr in self.config:
value = self.config.get(attr)
return self.clean(value, delimiter, silent)
def load_from_key_store(self, f=None, delimiter=':', silent=False):
if f is None:
f = self.key_store
return super(ServiceAccessKey, self).load_from_f(f, delimiter, silent)
def load_from_conf_all(self, **kwargs):
"""Should return access_key_id, access_key_secret""" """Should return access_key_id, access_key_secret"""
for method in [self.load_from_conf_setting, for method in [self.load_from_env,
self.load_from_key_store, self.load_from_conf,
self.load_from_conf_env]: self.load_from_key_store]:
try: try:
return method(**kwargs) return method(**kwargs)
except LoadAccessKeyError: except LoadAccessKeyError:
continue continue
return None
if not (bool(self.id) and bool(self.secret)): def save_to_key_store(self):
logging.error('Load access key failed') return super().save_to_f(self._key_store)
\ No newline at end of file
def save_to_key_store(self, key_store=None, silent=True):
if key_store is None:
key_store = self.key_store
return super(ServiceAccessKey, self).save_to_f(key_store, silent)
\ No newline at end of file
...@@ -5,22 +5,25 @@ import os ...@@ -5,22 +5,25 @@ import os
import json import json
import base64 import base64
import logging import logging
import sys
import paramiko import paramiko
import requests import requests
import time
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from cachetools import cached, TTLCache from cachetools import cached, TTLCache
from .auth import Auth, ServiceAccessKey, AccessKey from .auth import AppAccessKey, AccessKeyAuth
from .utils import sort_assets, PKey, timestamp_to_datetime_str from .utils import sort_assets, PKey, timestamp_to_datetime_str
from .exception import RequestError, LoadAccessKeyError, ResponseError from .exception import RequestError, ResponseError
_USER_AGENT = 'jms-sdk-py' _USER_AGENT = 'jms-sdk-py'
CACHED_TTL = os.environ.get('CACHED_TTL', 30) CACHED_TTL = os.environ.get('CACHED_TTL', 30)
logger = logging.getLogger(__file__)
API_URL_MAPPING = { API_URL_MAPPING = {
'terminal-register': '/api/applications/v1/terminal/register/', 'terminal-register': '/api/applications/v1/terminal/',
'terminal-heatbeat': '/api/applications/v1/terminal/heatbeat/', 'terminal-heatbeat': '/api/applications/v1/terminal/heatbeat/',
'send-proxy-log': '/api/audits/v1/proxy-log/receive/', 'send-proxy-log': '/api/audits/v1/proxy-log/receive/',
'finish-proxy-log': '/api/audits/v1/proxy-log/%s/', 'finish-proxy-log': '/api/audits/v1/proxy-log/%s/',
...@@ -38,21 +41,13 @@ API_URL_MAPPING = { ...@@ -38,21 +41,13 @@ API_URL_MAPPING = {
} }
class FakeResponse(object):
def __init__(self):
self.status_code = 500
@staticmethod
def json():
return {}
class Request(object): class Request(object):
methods = { methods = {
'get': requests.get, 'get': requests.get,
'post': requests.post, 'post': requests.post,
'patch': requests.patch, 'patch': requests.patch,
'put': requests.put, 'put': requests.put,
'delete': requests.delete,
} }
def __init__(self, url, method='get', data=None, params=None, def __init__(self, url, method='get', data=None, params=None,
...@@ -76,23 +71,23 @@ class Request(object): ...@@ -76,23 +71,23 @@ class Request(object):
return result return result
class AppRequest(object): class AppRequests(object):
def __init__(self, endpoint, auth=None): def __init__(self, endpoint, auth=None):
self._auth = auth self.auth = auth
self.endpoint = endpoint self.endpoint = endpoint
@staticmethod @staticmethod
def clean_result(resp): def clean_result(resp):
if resp.status_code >= 400: if resp.status_code >= 500:
return ResponseError("Response code is {0.code}: {0.text}".format(resp)) raise ResponseError("Response code is {0.code}: {0.text}".format(resp))
try: try:
result = resp.json() _ = resp.json()
except json.JSONDecodeError: except json.JSONDecodeError:
return RequestError("Response json couldn't be decode: {0.text}".format(resp)) raise ResponseError("Response json couldn't be decode: {0.text}".format(resp))
else: else:
return result return resp
def do(self, api_name=None, pk=None, method='get', use_auth=True, def do(self, api_name=None, pk=None, method='get', use_auth=True,
data=None, params=None, content_type='application/json'): data=None, params=None, content_type='application/json'):
...@@ -108,15 +103,15 @@ class AppRequest(object): ...@@ -108,15 +103,15 @@ class AppRequest(object):
req = Request(url, method=method, data=data, req = Request(url, method=method, data=data,
params=params, content_type=content_type) params=params, content_type=content_type)
if use_auth: if use_auth:
if not self._auth: if not self.auth:
raise RequestError('Authentication required') raise RequestError('Authentication required')
else: else:
self._auth.sign_request(req) self.auth.sign_request(req)
try: try:
resp = req.do() resp = req.do()
except (requests.ConnectionError, requests.ConnectTimeout) as e: except (requests.ConnectionError, requests.ConnectTimeout) as e:
return RequestError("Connect endpoint: {} {}".format(self.endpoint, e)) raise RequestError("Connect endpoint {} error: {}".format(self.endpoint, e))
return self.clean_result(resp) return self.clean_result(resp)
...@@ -138,122 +133,90 @@ class AppRequest(object): ...@@ -138,122 +133,90 @@ class AppRequest(object):
class AppService: class AppService:
"""使用该类和Jumpserver api进行通信,将terminal用到的常用api进行了封装, access_key_class = AppAccessKey
直接调用方法即可.
from jms import AppService
service = AppService(app_name='coco', endpoint='http://localhost:8080')
# 如果app是第一次启动, 注册一下,并得到 access key, 然后认真
service.register()
service.auth() # 直接使用注册得到的access key进行认证
# 如果已经启动过, 需要使用access key进行认证
service.auth(access_key_id, access_key_secret)
service.check_auth() # 检测一下是否认证有效
data = {
"username": "ibuler",
"name": "Guanghongwei",
"hostname": "localhost",
"ip": "127.0.0.1",
"system_user": "web",
"login_type": "ST",
"is_failed": 0,
"date_start": 1484206685,
}
service.send_proxy_log(data)
"""
access_key_class = ServiceAccessKey
def __init__(self, app): def __init__(self, app):
self.app = 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 self.access_key = None
self.requests = AppRequests(app.config['JUMPSERVER_ENDPOINT'])
self.prepare()
def prepare(self):
self.load_access_key()
self.set_auth()
self.valid_auth()
def load_access_key(self): def load_access_key(self):
# Must be get access key if not register it # Must be get access key if not register it
self.access_key = ServiceAccessKey(self).load() self.access_key = self.access_key_class(self.app).load()
if self.access_key is None: if self.access_key is None:
self.register_and_wait_for_accept() self.register_and_save()
self.save_key_to_store()
def set_auth(self):
def register_and_wait_for_accept(self): self.requests.auth = AccessKeyAuth(self.access_key)
"""注册Terminal, 通常第一次启动需要向Jumpserver注册
def valid_auth(self):
content: { while True:
'terminal': {'id': 1, 'name': 'terminal name', ...}, delay = 1
'user': { if self.heatbeat() is None:
'username': 'same as terminal name', msg = "Access key is not valid or need admin " \
'name': 'same as username', "accepted, waiting %d s" % delay
}, logger.info(msg)
'access_key_id': 'ACCESS KEY ID', delay += 1
'access_key_secret': 'ACCESS KEY SECRET', time.sleep(1)
} else:
""" break
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): def register_and_save(self):
"""App认证, 请求api需要签名header self.register()
:param access_key_id: 注册时或新建app用户生成access key id self.save_access_key()
:param access_key_secret: 同上access key secret
"""
if None not in (access_key_id, access_key_secret):
self.access_key.id = access_key_id
self.access_key.secret = access_key_secret
self._auth = Auth(access_key_id=self.access_key.id, def save_access_key(self):
access_key_secret=self.access_key.secret) self.access_key.save_to_key_store()
def auth_magic(self): def register(self):
"""加载配置文件定义的变量,尝试从配置文件, Keystore, 环境变量加载 try:
Access Key 然后进行认证 resp = self.requests.post('terminal-register',
""" data={'name': self.app.name},
self.access_key = self.access_key_class(config=self.config) use_auth=False)
self.access_key.load_from_conf_all() except (RequestError, ResponseError) as e:
if self.access_key: logger.error(e)
self._auth = Auth(access_key_id=self.access_key.id, sys.exit()
access_key_secret=self.access_key.secret)
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
)
logger.info('Register app success: %s' % access_key_id,)
elif resp.status_code == 400:
msg = '{} exist already, register failed'.format(self.app.name)
logging.error(msg)
sys.exit()
else: else:
raise LoadAccessKeyError('Load access key all failed, auth ignore') logging.error('Register terminal {} failed unknown'.format(self.app.name))
sys.exit()
def wait_util_accept(self):
while True:
if self.heatbeat() is None:
time.sleep(1)
else:
break
def terminal_heatbeat(self): def heatbeat(self):
"""和Jumpserver维持心跳, 当Terminal断线后,jumpserver可以知晓 """和Jumpserver维持心跳, 当Terminal断线后,jumpserver可以知晓
Todo: Jumpserver发送的任务也随heatbeat返回, 并执行,如 断开某用户 Todo: Jumpserver发送的任务也随heatbeat返回, 并执行,如 断开某用户
""" """
r, content = self.post('terminal-heatbeat', use_auth=True) r, content = self.requests.post('terminal-heatbeat', use_auth=True)
if r.status_code == 201: if r.status_code == 201:
return content return content
else: else:
return None return None
def is_authenticated(self):
"""执行auth后只是构造了请求头, 可以使用该方法连接Jumpserver测试认证"""
result = self.terminal_heatbeat()
return result
def validate_user_asset_permission(self, user_id, asset_id, system_user_id): def validate_user_asset_permission(self, user_id, asset_id, system_user_id):
"""验证用户是否有登录该资产的权限""" """验证用户是否有登录该资产的权限"""
params = { params = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment