第三方服务集成所需密钥管理功能完成。

feature/assist-websocket
Apex Liu 2022-05-27 19:16:57 +08:00
parent 8b106a8be6
commit d92cf2f469
13 changed files with 429 additions and 351 deletions

View File

@ -942,6 +942,7 @@ def main():
if command == 'ext-client':
builder.build_jsoncpp()
builder.build_mongoose()
builder.build_zlib()
builder.build_openssl()
elif command == 'ext-server':
builder.prepare_python()

View File

@ -10,7 +10,7 @@ mbedtls = 2.16.3
jsoncpp = 1.9.2
; https://github.com/cesanta/mongoose/releases
mongoose = 6.16
; https://www.zlib.net/zlib1211.zip
zlib = 1.2.11,1211
; https://www.zlib.net/zlib1212.zip
zlib = 1.2.12,1212
; https://git.libssh.org/projects/libssh.git/
libssh = 0.8.9

View File

@ -60,7 +60,8 @@ int ts_web_rpc_get_conn_info(int conn_id, TS_CONNECT_INFO& info)
EXLOGE("[core] get conn info from web-server failed: can not connect to web-server.\n");
return TPE_NETWORK;
}
if (body.length() == 0) {
if (body.length() == 0)
{
EXLOGE("[core] get conn info from web-server failed: got nothing.\n");
return TPE_NETWORK;
}
@ -188,8 +189,9 @@ int ts_web_rpc_get_conn_info(int conn_id, TS_CONNECT_INFO& info)
// 进一步判断参数是否合法
// 注意account_id可以为-1表示这是一次测试连接。
if (user_id <= 0 || host_id <= 0
|| user_username.length() == 0
// if (user_id <= 0 || host_id <= 0
// ||
if (user_username.length() == 0
|| host_ip.length() == 0 || conn_ip.length() == 0 || client_ip.length() == 0
|| conn_port <= 0 || conn_port >= 65535
|| acc_username.length() == 0
@ -200,11 +202,13 @@ int ts_web_rpc_get_conn_info(int conn_id, TS_CONNECT_INFO& info)
return TPE_PARAM;
}
if(auth_type != TP_AUTH_TYPE_NONE && acc_secret.length() == 0) {
if (auth_type != TP_AUTH_TYPE_NONE && acc_secret.length() == 0)
{
return TPE_PARAM;
}
if (_enc && !acc_secret.empty()) {
if (_enc && !acc_secret.empty())
{
ex_astr _auth;
if (!ts_db_field_decrypt(acc_secret, _auth))
return TPE_FAILED;
@ -298,7 +302,8 @@ bool ts_web_rpc_session_begin(TS_CONNECT_INFO& info, int& record_id)
return true;
}
bool ts_web_rpc_session_update(int record_id, int protocol_sub_type, int state) {
bool ts_web_rpc_session_update(int record_id, int protocol_sub_type, int state)
{
//Json::FastWriter json_writer;
Json::Value jreq;
jreq["method"] = "session_update";

View File

@ -297,7 +297,7 @@ void SshSession::_thread_loop()
if (t_now - t_last_send_keepalive >= 60)
{
t_last_send_keepalive = t_now;
EXLOGD("[%s] send keepalive.\n", m_dbg_name.c_str());
// EXLOGD("[%s] send keepalive.\n", m_dbg_name.c_str());
ssh_send_ignore(m_rs_tp2cli, "keepalive@openssh.com");
ssh_send_ignore(m_rs_tp2srv, "keepalive@openssh.com");
}

View File

@ -841,6 +841,8 @@ $app.create_config_integration = function () {
title: '权限角色',
key: 'role_name',
width: 120,
render: 'role_name',
fields: {role_name: 'role_name'}
},
{
title: '',
@ -907,6 +909,13 @@ $app.create_config_integration = function () {
return '<span class="mono">' + fields.acc_key + '</span>';
};
render.role_name = function(row_id, fields) {
if(!_.isNull(fields.role_name) && fields.role_name.length > 0)
return fields.role_name;
else
return '<span class="label label-sm label-danger">尚未设置</span>';
};
render.make_action_btn = function (row_id, fields) {
let ret = [];
ret.push('<div class="btn-group btn-group-sm" role="group">');

View File

@ -1,82 +0,0 @@
# -*- coding: utf-8 -*-
import os
import json
from app.base.configs import tp_cfg
from app.base.logger import log
class ExtSrvCfg(object):
def __init__(self):
super().__init__()
import builtins
if '__ext_srv_cfg__' in builtins.__dict__:
raise RuntimeError('ExtSrvCfg object exists, you can not create more than one instance.')
# session表session_id为索引每个项为一个字典包括 v(value), t(last access), e(expire seconds)
self._cfg = dict()
def init(self):
cfg = tp_cfg()
cfg_file = os.path.join(cfg.cfg_path, 'extsrv.json')
# 如果配置文件不存在则不支持第三方服务调用TP-API
if not os.path.exists(cfg_file):
return True
log.i('Loading external server configuration...\n')
with open(cfg_file, encoding='utf_8') as f:
c = f.read()
try:
sc = json.loads(c)
except:
return False
if 'version' not in sc:
return False
if 'ext_srv' not in sc:
return False
srv = sc['ext_srv']
try:
for i in range(len(srv)):
srv_name = srv[i]['name']
srv_desc = srv[i]['desc']
for j in range(len(srv[i]['access'])):
key = srv[i]['access'][j]['key']
secret = srv[i]['access'][j]['secret']
privilege = int(srv[i]['access'][j]['privilege'])
if key in self._cfg:
log.e('Invalid extsrv.json, duplicated key: {}\n'.format(key))
return False
self._cfg[key] = {
'name': srv_name,
'desc': srv_desc,
'secret': secret,
'privilege': privilege
}
except:
log.e('Invalid extsrv.json\n')
return False
return True
def get_secret_info(self, key):
if key not in self._cfg:
return None
return self._cfg[key]
def tp_ext_srv_cfg():
"""
:rtype : ExtSrvCfg
"""
import builtins
if '__ext_srv_cfg__' not in builtins.__dict__:
builtins.__dict__['__ext_srv_cfg__'] = ExtSrvCfg()
return builtins.__dict__['__ext_srv_cfg__']

View File

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
import datetime
import threading
from app.const import *
from app.base.configs import tp_cfg
from app.base.cron import tp_cron
import app.model.system as system_model
class IntegrationManager(object):
"""
第三方系统集成密钥-内存缓存管理
"""
def __init__(self):
super().__init__()
import builtins
if '__integration_manager__' in builtins.__dict__:
raise RuntimeError('IntegrationManager object exists, you can not create more than one instance.')
# 密钥表access-key为索引每个项为一个字典包括 n(name), s(access-secret), i(id), r(role_id), p(privilege)
self._keys = dict()
self._lock = threading.RLock()
def init(self):
# load from database.
err, _, _, recorder = system_model.get_integration(with_acc_sec=True)
if err != TPE_OK:
return False
# print(recorder)
# [{'id': 8, 'acc_key': 'TPRTn6c7xMW7ci7f', 'name': 'test-audit', 'comment': '日常审计操作', 'acc_sec': 'y3NcQZPdy76kPQmNz7nTik72S8JrTmnp', 'role_id': 3, 'role_name': '审计员', 'privilege': 32769}, ...]
for i in recorder:
self._keys[i['acc_key']] = {
'name': i['name'],
'secret': i['acc_sec'],
'id': i['id'],
'role_id': i['role_id'],
'privilege': i['privilege']
}
# tp_cron().add_job('session_expire', self._check_expire, first_interval_seconds=None, interval_seconds=60)
return True
def get_secret(self, acc_key):
with self._lock:
return None if acc_key not in self._keys else self._keys[acc_key]
def update_by_id(self, _id, acc_key, acc_sec, name, role_id, privilege):
with self._lock:
if acc_key in self._keys:
self._keys[acc_key]['id'] = _id
self._keys[acc_key]['name'] = name
self._keys[acc_key]['role_id'] = role_id
self._keys[acc_key]['privilege'] = privilege
else:
self._keys[acc_key] = {
'id': _id,
'secret': '',
'name': name,
'role_id': role_id,
'privilege': privilege
}
if acc_sec is not None:
self._keys[acc_key]['secret'] = acc_sec
print(self._keys)
def update_by_role_id(self, role_id, privilege):
with self._lock:
for i in self._keys:
if self._keys[i]['role_id'] == role_id:
self._keys[i]['privilege'] = privilege
print(self._keys)
def remove_by_id(self, ids):
with self._lock:
key_to_remove = list()
for i in self._keys:
if self._keys[i]['id'] in ids:
key_to_remove.append(i)
for k in key_to_remove:
del self._keys[k]
print(self._keys)
def tp_integration():
"""
取得第三方服务集成密钥管理器的唯一实例
:rtype : IntegrationManager
"""
import builtins
if '__integration_manager__' not in builtins.__dict__:
builtins.__dict__['__integration_manager__'] = IntegrationManager()
return builtins.__dict__['__integration_manager__']

View File

@ -16,7 +16,6 @@ import tornado.web
import tornado.platform.asyncio
from app.const import *
from app.base.configs import tp_cfg
from app.base.extsrv import tp_ext_srv_cfg
from app.base.db import get_db
from app.base.logger import log
from app.base.session import tp_session
@ -26,6 +25,7 @@ from app.base.host_alive import tp_host_alive
from app.base.utils import tp_generate_random
from app.app_ver import TP_SERVER_VER
from app.base.assist_bridge import tp_assist_bridge
from app.base.integration import tp_integration
class WebApp:
@ -100,10 +100,6 @@ class WebApp:
return 0
def _run_loop(self):
ext_srv_cfg = tp_ext_srv_cfg()
if not ext_srv_cfg.init():
return 0
log.i('Teleport Web Server starting ...\n')
tp_cron().init()
@ -157,6 +153,10 @@ class WebApp:
log.e('can not initialize system status collector.\n')
return 0
if not tp_integration().init():
log.e('can not load integration config.\n')
return 0
if cfg.common.check_host_alive:
if not tp_host_alive().init():
log.e('can not initialize host state inspector.\n')

View File

@ -11,7 +11,8 @@ from app.model import host
from app.base.logger import *
from app.base.controller import TPBaseJsonHandler
from app.base.utils import tp_bin, tp_str, tp_timestamp_sec
from app.base.extsrv import tp_ext_srv_cfg
# from app.base.extsrv import tp_ext_srv_cfg
from app.base.integration import tp_integration
from .ops import api_request_session_id
@ -43,7 +44,8 @@ def _parse_api_args(handler):
return False, handler.write_json(TPE_PARAM)
# 从数据库中根据access-key查找access-secret
sec_info = tp_ext_srv_cfg().get_secret_info(req_access_key)
# sec_info = tp_ext_srv_cfg().get_secret_info(req_access_key)
sec_info = tp_integration().get_secret(req_access_key)
if sec_info is None:
return False, handler.write_json(TPE_INVALID_API_KEY)
access_secret = sec_info['secret']
@ -67,6 +69,7 @@ def _parse_api_args(handler):
return False, handler.write_json(TPE_JSON_FORMAT)
args['_srv_name_'] = sec_info['name']
args['_privilege_'] = sec_info['privilege']
# log.d('api:get_host, param=', args, '\n')
@ -106,6 +109,7 @@ class RequestSessionHandler(TPBaseJsonHandler):
acc_id = args['account_id']
operator = args['operator']
protocol_sub_type = args['protocol_sub_type']
privilege = args['_privilege_']
except:
return self.write_json(TPE_PARAM)
@ -115,7 +119,8 @@ class RequestSessionHandler(TPBaseJsonHandler):
acc_id,
protocol_sub_type,
self.request.remote_ip,
operator
operator,
privilege
)
if ret['code'] != TPE_OK:

View File

@ -12,7 +12,8 @@ from app.model import host
from app.base.logger import *
from app.base.controller import TPBaseJsonHandler
from app.base.utils import tp_bin, tp_str, tp_timestamp_sec
from app.base.extsrv import tp_ext_srv_cfg
# from app.base.extsrv import tp_ext_srv_cfg
from app.base.integration import tp_integration
from .ops import api_v2_request_session_id
@ -53,7 +54,8 @@ def _parse_api_args(handler):
return False, handler.write_json(TPE_PARAM)
# 从数据库中根据access-key查找access-secret
sec_info = tp_ext_srv_cfg().get_secret_info(req_access_key)
# sec_info = tp_ext_srv_cfg().get_secret_info(req_access_key)
sec_info = tp_integration().get_secret(req_access_key)
if sec_info is None:
return False, handler.write_json(TPE_INVALID_API_KEY)
access_secret = sec_info['secret']
@ -93,7 +95,7 @@ def _parse_api_args(handler):
return False, handler.write_json(TPE_JSON_FORMAT)
args['_srv_name_'] = sec_info['name']
args['_privilege'] = sec_info['privilege']
args['_privilege_'] = sec_info['privilege']
return True, args
@ -115,6 +117,7 @@ class RequestSessionHandler(TPBaseJsonHandler):
remote_secret = args['remote_secret']
protocol_type = args['protocol_type']
protocol_sub_type = args['protocol_sub_type']
privilege = args['_privilege_']
except:
return self.write_json(TPE_PARAM)
@ -122,7 +125,7 @@ class RequestSessionHandler(TPBaseJsonHandler):
ret = yield api_v2_request_session_id(
remote_ip, remote_port, remote_auth_type, remote_user, remote_secret,
protocol_type, protocol_sub_type, self.request.remote_ip, operator
protocol_type, protocol_sub_type, self.request.remote_ip, operator, privilege
)
if ret['code'] != TPE_OK:
@ -141,7 +144,7 @@ class RequestAccessTokenHandler(TPBaseJsonHandler):
try:
operator = args['operator']
privilege = args['_privilege']
privilege = args['_privilege_']
except:
return self.write_json(TPE_PARAM)

View File

@ -92,13 +92,19 @@ class SessionListsHandler(TPBaseHandler):
@tornado.gen.coroutine
def api_request_session_id(acc_id, protocol_sub_type, client_ip, operator):
def api_request_session_id(acc_id, protocol_sub_type, client_ip, operator, privilege):
ret = {
'code': TPE_OK,
'message': '',
'data': {}
}
# 检查权限
if (privilege & TP_PRIVILEGE_OPS) == 0:
ret['code'] = TPE_PRIVILEGE
ret['message'] = '权限不足'
return ret
conn_info = dict()
conn_info['_enc'] = 1
conn_info['host_id'] = 0
@ -106,7 +112,6 @@ def api_request_session_id(acc_id, protocol_sub_type, client_ip, operator):
conn_info['user_id'] = 1
conn_info['user_username'] = operator
# 直接连接(无需授权,第三方服务操作,已经经过授权检查了)
err, acc_info = account.get_account_info(acc_id)
if err != TPE_OK:
ret['code'] = err
@ -198,19 +203,24 @@ def api_request_session_id(acc_id, protocol_sub_type, client_ip, operator):
@tornado.gen.coroutine
def api_v2_request_session_id(
remote_ip, remote_port, remote_auth_type, remote_user, remote_secret,
protocol_type, protocol_sub_type, client_ip, operator):
protocol_type, protocol_sub_type, client_ip, operator, privilege):
ret = {
'code': TPE_OK,
'message': '',
'data': {}
}
# 直接连接(无需授权,第三方服务操作,已经经过授权检查了)
# 检查权限
if (privilege & TP_PRIVILEGE_OPS) == 0:
ret['code'] = TPE_PRIVILEGE
ret['message'] = '权限不足'
return ret
conn_info = dict()
conn_info['_enc'] = 0
conn_info['host_id'] = 1
conn_info['host_id'] = 0
conn_info['client_ip'] = client_ip
conn_info['user_id'] = 1
conn_info['user_id'] = 0
conn_info['user_username'] = operator
conn_info['host_ip'] = remote_ip

View File

@ -13,6 +13,7 @@ from app.base import mail
from app.base.configs import tp_cfg
from app.base.controller import TPBaseHandler, TPBaseJsonHandler
from app.base.logger import *
from app.base.integration import tp_integration
from app.const import *
from app.base.db import get_db
from app.model import syslog
@ -132,6 +133,9 @@ class DoRoleUpdateHandler(TPBaseJsonHandler):
return self.write_json(TPE_FAILED, '禁止修改系统管理员角色!')
err = system_model.update_role(self, role_id, role_name, privilege)
if err == TPE_OK:
tp_integration().update_by_role_id(role_id, privilege)
return self.write_json(err, data=role_id)
@ -160,6 +164,9 @@ class DoRoleRemoveHandler(TPBaseJsonHandler):
return self.write_json(TPE_FAILED, '禁止删除系统管理员角色!')
err = system_model.remove_role(self, role_id)
if err == TPE_OK:
tp_integration().update_by_role_id(role_id, 0)
return self.write_json(err)
@ -806,19 +813,19 @@ class DoUpdateIntegrationHandler(TPBaseJsonHandler):
_role_id = int(args['role_id'])
_name = args['name']
_comment = args['comment']
if _id != -1:
_acc_key = args['acc_key']
_regenerate = args['regenerate']
_acc_key = None if _id == -1 else args['acc_key']
_regenerate = False if _id == -1 else args['regenerate']
except:
return self.write_json(TPE_PARAM)
ret = dict()
if _id == -1:
err, acc_key, acc_sec = system_model.create_integration(self, _role_id, _name, _comment)
err, _id, acc_key, acc_sec, privilege = system_model.create_integration(self, _role_id, _name, _comment)
else:
err, acc_key, acc_sec = system_model.update_integration(self, _id, _role_id, _name, _comment, _acc_key, _regenerate)
err, acc_key, acc_sec, privilege = system_model.update_integration(self, _id, _role_id, _name, _comment, _acc_key, _regenerate)
if err == TPE_OK:
tp_integration().update_by_id(_id, acc_key, acc_sec, _name, _role_id, privilege)
ret['acc_key'] = acc_key
ret['acc_sec'] = acc_sec
self.write_json(TPE_OK, data=ret)
@ -847,5 +854,7 @@ class DoRemoveIntegrationHandler(TPBaseJsonHandler):
return self.write_json(TPE_PARAM)
err = system_model.remove_integration(self, items)
if err == TPE_OK:
tp_integration().remove_by_id(items)
self.write_json(err)

View File

@ -134,24 +134,33 @@ def create_integration(handler, role_id, name, comment):
acc_sec = tp_gen_password(32)
for i in range(20):
_acc_key = 'TP{}'.format(tp_gen_password(14))
sql = 'SELECT `id` FROM {dbtp}integration_auth WHERE `acc_key`="{acc_key}";'.format(dbtp=db.table_prefix, acc_key=_acc_key)
db_ret = db.query(sql)
sql = 'SELECT `id` FROM {dbtp}integration_auth WHERE `acc_key`={ph};'.format(dbtp=db.table_prefix, ph=db.place_holder)
db_ret = db.query(sql, (_acc_key, ))
if db_ret is not None and len(db_ret) > 0:
continue
acc_key = _acc_key
break
if len(acc_key) == 0:
return TPE_FAILED, None, None
return TPE_FAILED
# 查询对应的角色的权限
sql = 'SELECT `privilege` FROM {tp}role WHERE `id`={ph}'.format(tp=db.table_prefix, ph=db.place_holder)
db_ret = db.query(sql, (role_id, ))
if db_ret is None or len(db_ret) != 1:
return TPE_DATABASE
privilege = db_ret[0][0]
sql = 'INSERT INTO `{dbtp}integration_auth` (`acc_key`, `acc_sec`, `role_id`, `name`, `comment`, `creator_id`, `create_time`) VALUES ' \
'("{acc_key}", "{acc_sec}", {role_id}, "{name}", "{comment}", {creator_id}, {create_time});' \
''.format(dbtp=db.table_prefix, acc_key=acc_key, acc_sec=acc_sec, role_id=role_id, name=name, comment=comment, creator_id=operator['id'], create_time=_time_now)
db_ret = db.exec(sql)
if not db_ret:
return TPE_DATABASE, None, None
return TPE_DATABASE
_id = db.last_insert_id()
syslog.sys_log(operator, handler.request.remote_ip, TPE_OK, "创建外部密钥 {}".format(name))
return TPE_OK, acc_key, acc_sec
return TPE_OK, _id, acc_key, acc_sec, privilege
def update_integration(handler, _id, role_id, name, comment, acc_key, regenerate):
@ -167,6 +176,13 @@ def update_integration(handler, _id, role_id, name, comment, acc_key, regenerate
if db_ret is None or len(db_ret) == 0:
return TPE_NOT_EXISTS, None, None
# 查询对应的角色的权限
sql = 'SELECT `privilege` FROM {tp}role WHERE `id`={ph}'.format(tp=db.table_prefix, ph=db.place_holder)
db_ret = db.query(sql, (role_id, ))
if db_ret is None or len(db_ret) != 1:
return TPE_DATABASE
privilege = db_ret[0][0]
if regenerate:
acc_sec = tp_gen_password(32)
sql = 'UPDATE `{dbtp}integration_auth` SET `name`={ph}, `comment`={ph}, `role_id`={ph}, `acc_sec`={ph} WHERE `id`={ph};' \
@ -181,7 +197,7 @@ def update_integration(handler, _id, role_id, name, comment, acc_key, regenerate
return TPE_DATABASE, None, None
syslog.sys_log(operator, handler.request.remote_ip, TPE_OK, "更新外部密钥 {}{}".format(name, '重新生成access-secret。' if regenerate else ''))
return TPE_OK, acc_key, acc_sec
return TPE_OK, acc_key, acc_sec, privilege
def remove_integration(handler, items):