增强:强制中断在线会话功能实现了,SSH协议已测试,RDP和TELNET协议尚未实现。

pull/105/head
Apex Liu 2018-05-04 01:30:54 +08:00
parent 685f31f566
commit 3eb59eb071
18 changed files with 941 additions and 727 deletions

View File

@ -14,6 +14,9 @@
# define TPP_API
#endif
#define TPP_CMD_INIT 0x00000000
#define TPP_CMD_KILL_SESSIONS 0x00000006
typedef struct TPP_CONNECT_INFO
{
char* sid;
@ -79,6 +82,8 @@ extern "C"
TPP_API void tpp_timer(void);
TPP_API void tpp_set_cfg(TPP_SET_CFG_ARGS* cfg_args);
TPP_API ex_rv tpp_command(ex_u32 cmd, const char* param);
#ifdef __cplusplus
}
#endif
@ -89,4 +94,6 @@ typedef ex_rv(*TPP_STOP_FUNC)(void);
typedef void(*TPP_TIMER_FUNC)(void);
typedef void(*TPP_SET_CFG_FUNC)(TPP_SET_CFG_ARGS* cfg_args);
typedef ex_rv(*TPP_COMMAND_FUNC)(ex_u32 cmd, const char* param); // param is a JSON formatted string.
#endif // __TP_PROTOCOL_INTERFACE_H__

View File

@ -47,15 +47,17 @@ bool TppManager::load_tpp(const ex_wstr& libname)
lib->stop = (TPP_STOP_FUNC)GetProcAddress(lib->dylib, "tpp_stop");
lib->timer = (TPP_TIMER_FUNC)GetProcAddress(lib->dylib, "tpp_timer");
lib->set_cfg = (TPP_SET_CFG_FUNC)GetProcAddress(lib->dylib, "tpp_set_cfg");
lib->command = (TPP_COMMAND_FUNC)GetProcAddress(lib->dylib, "tpp_command");
#else
lib->init = (TPP_INIT_FUNC)dlsym(lib->dylib, "tpp_init");
lib->start = (TPP_START_FUNC)dlsym(lib->dylib, "tpp_start");
lib->stop = (TPP_STOP_FUNC)dlsym(lib->dylib, "tpp_stop");
lib->timer = (TPP_TIMER_FUNC)dlsym(lib->dylib, "tpp_timer");
lib->set_cfg = (TPP_SET_CFG_FUNC)dlsym(lib->dylib, "tpp_set_cfg");
lib->command = (TPP_COMMAND_FUNC)dlsym(lib->dylib, "tpp_command");
#endif
if (lib->init == NULL || lib->start == NULL || lib->stop == NULL || lib->timer == NULL || lib->set_cfg == NULL)
if (lib->init == NULL || lib->start == NULL || lib->stop == NULL || lib->timer == NULL || lib->set_cfg == NULL || lib->command == NULL)
{
EXLOGE(L"[core] load dylib `%ls` failed, can not locate all functions.\n", libfile.c_str());
delete lib;
@ -118,3 +120,12 @@ void TppManager::set_config(int noop_timeout) {
(*it)->set_cfg(&args);
}
}
void TppManager::kill_sessions(const ex_astr& sessions) {
tpp_libs::iterator it = m_libs.begin();
for (; it != m_libs.end(); ++it)
{
(*it)->command(TPP_CMD_KILL_SESSIONS, sessions.c_str());
}
}

View File

@ -25,6 +25,8 @@ typedef struct TPP_LIB
TPP_STOP_FUNC stop;
TPP_TIMER_FUNC timer;
TPP_SET_CFG_FUNC set_cfg;
TPP_COMMAND_FUNC command;
}TPP_LIB;
typedef std::list<TPP_LIB*> tpp_libs;
@ -51,6 +53,7 @@ public:
int count(void) { return m_libs.size(); }
void set_config(int noop_timeout);
void kill_sessions(const ex_astr& sessions);
private:
tpp_libs m_libs;

View File

@ -255,6 +255,9 @@ void TsHttpRpc::_process_request(const ex_astr& func_cmd, const Json::Value& jso
if (func_cmd == "request_session") {
_rpc_func_request_session(json_param, buf);
}
else if (func_cmd == "kill_sessions") {
_rpc_func_kill_sessions(json_param, buf);
}
else if (func_cmd == "get_config") {
_rpc_func_get_config(json_param, buf);
}
@ -378,6 +381,45 @@ void TsHttpRpc::_rpc_func_request_session(const Json::Value& json_param, ex_astr
_create_json_ret(buf, TPE_OK, jr_data);
}
void TsHttpRpc::_rpc_func_kill_sessions(const Json::Value& json_param, ex_astr& buf) {
/*
{
"sessions": ["0123456", "ABCDEF", ...]
}
*/
if (json_param.isArray())
{
_create_json_ret(buf, TPE_PARAM);
return;
}
if (json_param["sessions"].isNull() || !json_param["sessions"].isArray())
{
_create_json_ret(buf, TPE_PARAM);
return;
}
Json::Value s = json_param["sessions"];
int cnt = s.size();
for (int i = 0; i < cnt; ++i)
{
if (!s[i].isString()) {
_create_json_ret(buf, TPE_PARAM);
return;
}
}
EXLOGV("[core] kill %d sessions.\n", cnt);
ex_astr ss = s.toStyledString();
g_tpp_mgr.kill_sessions(ss);
_create_json_ret(buf, TPE_OK);
}
void TsHttpRpc::_rpc_func_enc(const Json::Value& json_param, ex_astr& buf)
{
// https://github.com/eomsoft/teleport/wiki/TELEPORT-CORE-JSON-RPC#enc
@ -428,7 +470,7 @@ void TsHttpRpc::_rpc_func_set_config(const Json::Value& json_param, ex_astr& buf
// https://github.com/eomsoft/teleport/wiki/TELEPORT-CORE-JSON-RPC#set_config
/*
{
"noop-timeout": 900 # 900s = 15m
"noop-timeout": 15 #
}
*/
@ -453,7 +495,7 @@ void TsHttpRpc::_rpc_func_set_config(const Json::Value& json_param, ex_astr& buf
//static TppManager g_tpp_mgr;
EXLOGV("[core] no-op timeout set to %d minutes.\n", noop_timeout);
g_tpp_mgr.set_config(noop_timeout * 60);
g_tpp_mgr.set_config(noop_timeout * 60); // 内部按秒计,因此要 *60
// Json::Value jr_data;

View File

@ -37,6 +37,8 @@ private:
void _rpc_func_set_config(const Json::Value& json_param, ex_astr& buf);
// 请求一个会话ID
void _rpc_func_request_session(const Json::Value& json_param, ex_astr& buf);
// 强行终止会话
void _rpc_func_kill_sessions(const Json::Value& json_param, ex_astr& buf);
// 加密一个字符串返回的是密文的BASE64编码
void _rpc_func_enc(const Json::Value& json_param, ex_astr& buf);
// 要求整个核心服务退出

View File

@ -87,6 +87,20 @@ void SshProxy::set_cfg(TPP_SET_CFG_ARGS* args) {
m_noop_timeout_sec = args->noop_timeout;
}
void SshProxy::kill_sessions(const ex_astrs& sessions) {
ExThreadSmartLock locker(m_lock);
ts_ssh_sessions::iterator it;
for (it = m_sessions.begin(); it != m_sessions.end(); ++it) {
for (size_t i = 0; i < sessions.size(); ++i) {
if (it->first->sid() == sessions[i]) {
EXLOGW("[ssh] try to kill %s\n", sessions[i].c_str());
it->first->check_noop_timeout(0, 0); // Á¢¼´½áÊø
}
}
}
}
void SshProxy::_thread_loop()
{
EXLOGI("[ssh] TeleportServer-SSH ready on %s:%d\n", m_host_ip.c_str(), m_host_port);

View File

@ -16,6 +16,7 @@ public:
bool init();
void timer();
void set_cfg(TPP_SET_CFG_ARGS* args);
void kill_sessions(const ex_astrs& sessions);
void session_finished(SshSession* sess);

View File

@ -191,11 +191,14 @@ void SshSession::_check_channels() {
|| (cli == NULL && srv != NULL && ssh_channel_is_closed(srv))
|| (srv == NULL && cli != NULL && ssh_channel_is_closed(cli))
) {
if (cli)
if (cli) {
ssh_channel_free(cli);
if (srv)
cli = NULL;
}
if (srv) {
ssh_channel_free(srv);
srv = NULL;
}
_record_end((*it));
delete(*it);
@ -407,8 +410,11 @@ void SshSession::check_noop_timeout(ex_u32 t_now, ex_u32 timeout) {
for (; it != m_channels.end(); ++it) {
if ((*it)->need_close)
continue;
if (t_now - (*it)->last_access_timestamp > timeout) {
EXLOGW("[ssh] need close channel by timeout.\n");
if (t_now == 0)
EXLOGW("[ssh] try close channel by kill.\n");
else if (t_now - (*it)->last_access_timestamp > timeout)
EXLOGW("[ssh] try close channel by timeout.\n");
if (t_now == 0 || t_now - (*it)->last_access_timestamp > timeout) {
(*it)->need_close = true;
m_have_error = true;
}

View File

@ -79,6 +79,8 @@ public:
//
void check_noop_timeout(ex_u32 t_now, ex_u32 timeout);
const ex_astr& sid() { return m_sid; }
protected:
void _thread_loop(void);
void _set_stop_flag(void);

View File

@ -2,6 +2,8 @@
#include "tpp_env.h"
#include <teleport_const.h>
#include <json/json.h>
TPP_API ex_rv tpp_init(TPP_INIT_ARGS* init_args)
{
@ -42,3 +44,45 @@ TPP_API void tpp_timer(void) {
TPP_API void tpp_set_cfg(TPP_SET_CFG_ARGS* cfg_args) {
g_ssh_proxy.set_cfg(cfg_args);
}
static ex_rv _kill_sessions(const char* param) {
Json::Value jp;
Json::Reader jreader;
if (!jreader.parse(param, jp))
return TPE_JSON_FORMAT;
if (!jp.isArray())
return TPE_PARAM;
ex_astrs ss;
int cnt = jp.size();
for (int i = 0; i < cnt; ++i)
{
if (!jp[i].isString()) {
return TPE_PARAM;
}
ss.push_back(jp[i].asString());
}
g_ssh_proxy.kill_sessions(ss);
return TPE_PARAM;
}
TPP_API ex_rv tpp_command(ex_u32 cmd, const char* param) {
switch (cmd) {
case TPP_CMD_KILL_SESSIONS:
if (param == NULL || strlen(param) == 0)
return TPE_PARAM;
return _kill_sessions(param);
default:
return TPE_UNKNOWN_CMD;
}
return TPE_NOT_IMPLEMENT;
}

View File

@ -68,7 +68,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;TPP_EXPORTS;LIBSSH_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\..\..\common\teleport;..\..\..\..\common\libex\include;..\..\..\..\external\libssh-win-static\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>../../../../common\teleport;../../../../common\libex\include;../../../../external/jsoncpp/include;../../../../external\libssh-win-static\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
@ -86,7 +86,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;TPP_EXPORTS;LIBSSH_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\..\..\common\teleport;..\..\..\..\common\libex\include;..\..\..\..\external\libssh-win-static\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>../../../../common\teleport;../../../../common\libex\include;../../../../external/jsoncpp/include;../../../../external\libssh-win-static\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
@ -109,6 +109,7 @@
<ClInclude Include="..\..\..\..\common\libex\include\ex\ex_types.h" />
<ClInclude Include="..\..\..\..\common\libex\include\ex\ex_util.h" />
<ClInclude Include="..\..\..\..\common\libex\include\ex\ex_winsrv.h" />
<ClInclude Include="..\..\..\..\external\jsoncpp\include\json\json.h" />
<ClInclude Include="..\..\..\..\external\libssh-win-static\include\libssh\callbacks.h" />
<ClInclude Include="..\..\..\..\external\libssh-win-static\include\libssh\libssh.h" />
<ClInclude Include="..\..\..\..\external\libssh-win-static\include\libssh\server.h" />
@ -133,6 +134,9 @@
<ClCompile Include="..\..\..\..\common\libex\src\ex_thread.cpp" />
<ClCompile Include="..\..\..\..\common\libex\src\ex_util.cpp" />
<ClCompile Include="..\..\..\..\common\libex\src\ex_winsrv.cpp" />
<ClCompile Include="..\..\..\..\external\jsoncpp\src\lib_json\json_reader.cpp" />
<ClCompile Include="..\..\..\..\external\jsoncpp\src\lib_json\json_value.cpp" />
<ClCompile Include="..\..\..\..\external\jsoncpp\src\lib_json\json_writer.cpp" />
<ClCompile Include="..\..\common\base_env.cpp" />
<ClCompile Include="..\..\common\base_record.cpp" />
<ClCompile Include="..\..\common\ts_membuf.cpp" />

View File

@ -27,6 +27,9 @@
<Filter Include="libssh">
<UniqueIdentifier>{f1fea9ae-123c-4aa8-a152-e88d1d0a29fd}</UniqueIdentifier>
</Filter>
<Filter Include="jsoncpp">
<UniqueIdentifier>{022b0a3d-47c2-40d8-96d9-dceea02e7eef}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\..\common\libex\include\ex\ex_const.h">
@ -107,6 +110,9 @@
<ClInclude Include="..\..\..\..\external\libssh-win-static\include\libssh\server.h">
<Filter>libssh</Filter>
</ClInclude>
<ClInclude Include="..\..\..\..\external\jsoncpp\include\json\json.h">
<Filter>jsoncpp</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="tpssh.cpp">
@ -160,5 +166,14 @@
<ClCompile Include="ssh_recorder.cpp">
<Filter>main app</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\external\jsoncpp\src\lib_json\json_reader.cpp">
<Filter>jsoncpp</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\external\jsoncpp\src\lib_json\json_value.cpp">
<Filter>jsoncpp</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\external\jsoncpp\src\lib_json\json_writer.cpp">
<Filter>jsoncpp</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -80,7 +80,7 @@ $app.create_controls = function (cb_stack) {
fields: {time_begin: 'time_begin'}
},
{
title: '时',
title: '',
key: 'time_cost',
render: 'time_cost',
fields: {time_begin: 'time_begin', time_end: 'time_end', state: 'state'}

View File

@ -45,6 +45,13 @@ $app.create_controls = function (cb_stack) {
sort_asc: false,
fields: {id: 'id'}
},
{
title: '会话ID',
key: 'sid',
sort: true,
sort_asc: false,
fields: {sid: 'sid'}
},
{
title: '用户',
key: 'user',
@ -90,7 +97,7 @@ $app.create_controls = function (cb_stack) {
fields: {time_begin: 'time_begin'}
},
{
title: '时',
title: '',
key: 'time_cost',
render: 'time_cost',
fields: {time_begin: 'time_begin', time_end: 'time_end', state: 'state'}
@ -170,30 +177,22 @@ $app.create_controls = function (cb_stack) {
$app.on_table_session_cell_created = function (tbl, row_id, col_key, cell_obj) {
if (col_key === 'chkbox') {
cell_obj.find('[data-check-box]').click(function () {
// 同步相同会话ID的选中状态
var _obj = $(this);
var checked = _obj.is(':checked');
var _row_data = tbl.get_row(_obj);
var _objs = $('#' + $app.table_session.dom_id + ' tbody').find('[data-check-box]');
$.each(_objs, function (i, _o) {
var _rd = tbl.get_row(_o);
if (_row_data.sid === _rd.sid) {
$(_o).prop('checked', checked);
}
});
$app.check_host_all_selected();
});
}
// else if (col_key === 'action') {
// // 绑定系统选择框事件
// cell_obj.find('[data-action]').click(function () {
// var action = $(this).attr('data-action');
// if (action === 'edit') {
// $app.dlg_edit_host.show_edit(row_id);
// } else if (action === 'account') {
// $app.dlg_accounts.show(row_id);
// }
// });
// } else if (col_key === 'ip') {
// cell_obj.find('[data-toggle="popover"]').popover({trigger: 'hover'});
// // } else if (col_key === 'account') {
// // cell_obj.find('[data-action="add-account"]').click(function () {
// // $app.dlg_accounts.show(row_id);
// // });
// } else if (col_key === 'account_count') {
// cell_obj.find('[data-action="edit-account"]').click(function () {
// $app.dlg_accounts.show(row_id);
// });
// }
};
$app.check_host_all_selected = function (cb_stack) {
@ -389,18 +388,48 @@ $app.on_table_session_header_created = function (header) {
//header._table_ctrl.get_filter_ctrl('host_state').on_created();
};
$app.get_selected_session = function (tbl) {
$app.get_selected_sessions = function (tbl) {
var records = [];
var _objs = $('#' + $app.table_session.dom_id + ' tbody tr td input[data-check-box]');
$.each(_objs, function (i, _obj) {
if ($(_obj).is(':checked')) {
var _row_data = tbl.get_row(_obj);
records.push(_row_data.id);
records.push(_row_data.sid);
}
});
return records;
};
$app.on_btn_kill_sessions_click = function () {
$tp.notify_error('抱歉,此功能尚未实现!');
var sessions = $app.get_selected_sessions($app.table_session);
console.log(sessions);
if (sessions.length === 0) {
$tp.notify_error('请选择要强行终止的会话!');
return;
}
var _fn_sure = function (cb_stack, cb_args) {
$tp.ajax_post_json('/ops/kill', {sessions: sessions},
function (ret) {
if (ret.code === TPE_OK) {
$app.table_session.load_data();
$tp.notify_success('强行终止会话操作成功!');
} else {
$tp.notify_error('强行终止会话失败:' + tp_error_msg(ret.code, ret.message));
}
cb_stack.exec();
},
function () {
$tp.notify_error('网络故障,强行终止会话操作失败!');
cb_stack.exec();
}
);
};
var cb_stack = CALLBACK_STACK.create();
$tp.dlg_confirm(cb_stack, {
msg: '您确定要强行终止这些会话吗?',
fn_yes: _fn_sure
});
};

View File

@ -34,12 +34,12 @@
<!-- begin page-nav -->
<div class="table-extend-area">
## <div class="table-extend-cell checkbox-select-all"><input id="table-session-select-all" type="checkbox"/></div>
## <div class="table-extend-cell group-actions">
## <div class="btn-group" role="group">
## <button id="btn-kill-sessions" type="button" class="btn btn-danger"><i class="fa fa-times-circle fa-fw"></i> 强行中断</button>
## </div>
## </div>
<div class="table-extend-cell checkbox-select-all"><input id="table-session-select-all" type="checkbox"/></div>
<div class="table-extend-cell group-actions">
<div class="btn-group" role="group">
<button id="btn-kill-sessions" type="button" class="btn btn-danger"><i class="fa fa-times-circle fa-fw"></i> 强行中断</button>
</div>
</div>
<div class="table-extend-cell table-item-counter">
<ol id="table-session-paging"></ol>
</div>
@ -59,4 +59,10 @@
</div>
<!-- end of box -->
<div class="box">
<p>说明:</p>
<ul class="help-list">
<li>注意强制中断会话时相同会话ID的会话例如使用SecureCRT或者xShell客户端的“克隆会话”功能打开的会话均会被中断。</li>
</ul>
</div>
</div>

View File

@ -162,6 +162,8 @@ controllers = [
(r'/ops/get-remotes', ops.DoGetRemotesHandler),
# - [json] 构建授权映射表
(r'/ops/build-auz-map', ops.DoBuildAuzMapHandler),
# - [json] 强行终止指定会话
(r'/ops/kill', ops.DoKillSessionsHandler),
# ====================================================
# 审计相关

View File

@ -736,6 +736,32 @@ class DoGetRemotesHandler(TPBaseJsonHandler):
self.write_json(err, data=ret)
class DoKillSessionsHandler(TPBaseJsonHandler):
@tornado.gen.coroutine
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_OPS_AUZ)
if ret != TPE_OK:
return
args = self.get_argument('args', None)
if args is None:
return self.write_json(TPE_PARAM)
try:
args = json.loads(args)
except:
return self.write_json(TPE_JSON_FORMAT)
try:
sessions = args['sessions']
except:
return self.write_json(TPE_PARAM)
req = {'method': 'kill_sessions', 'param': {'sessions': sessions}}
_yr = core_service_async_post_http(req)
_err, _ = yield _yr
self.write_json(_err)
class DoBuildAuzMapHandler(TPBaseJsonHandler):
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_OPS_AUZ)

View File

@ -100,8 +100,8 @@ def get_records(handler, sql_filter, sql_order, sql_limit, sql_restrict, sql_exc
s.order_by('r.id', _sort)
elif 'time_begin' == sql_order['name']:
s.order_by('r.time_begin', _sort)
# elif 'os_type' == sql_order['name']:
# s.order_by('h.os_type', _sort)
elif 'sid' == sql_order['name']:
s.order_by('r.sid', _sort)
# elif 'cid' == sql_order['name']:
# s.order_by('h.cid', _sort)
# elif 'state' == sql_order['name']: