🔨 记录 RDP 传输日志.
This commit is contained in:
@@ -43,7 +43,7 @@ import javax.annotation.PostConstruct;
|
||||
public enum InputProtocolEnum {
|
||||
|
||||
/**
|
||||
* 连接终端
|
||||
* 请求连接
|
||||
*/
|
||||
CONNECT("co",
|
||||
TerminalConnectHandler.class,
|
||||
@@ -74,7 +74,17 @@ public enum InputProtocolEnum {
|
||||
new String[]{"type", "width", "height"},
|
||||
TerminalResizeRequest.class),
|
||||
|
||||
// ----------------------- SSH ----------------------
|
||||
// ----------------------- guacd ----------------------
|
||||
|
||||
/**
|
||||
* guacd 指令
|
||||
*/
|
||||
GUACD_INSTRUCTION("gi",
|
||||
GuacdInstructionHandler.class,
|
||||
new String[]{"type", "instruction"},
|
||||
GuacdInstructionRequest.class),
|
||||
|
||||
// ----------------------- ssh ----------------------
|
||||
|
||||
/**
|
||||
* SSH 输入
|
||||
@@ -84,7 +94,7 @@ public enum InputProtocolEnum {
|
||||
new String[]{"type", "command"},
|
||||
SshInputRequest.class),
|
||||
|
||||
// ----------------------- SFTP ----------------------
|
||||
// ----------------------- sftp ----------------------
|
||||
|
||||
/**
|
||||
* SFTP 文件列表
|
||||
@@ -182,15 +192,15 @@ public enum InputProtocolEnum {
|
||||
new String[]{"type", "path"},
|
||||
SftpBaseRequest.class),
|
||||
|
||||
// ----------------------- guacd ----------------------
|
||||
// ----------------------- rdp ----------------------
|
||||
|
||||
/**
|
||||
* guacd 指令
|
||||
* RDP 文件系统事件
|
||||
*/
|
||||
GUACD_INSTRUCTION("gi",
|
||||
GuacdInstructionHandler.class,
|
||||
new String[]{"type", "instruction"},
|
||||
GuacdInstructionRequest.class),
|
||||
RDP_FILE_SYSTEM_EVENT("fse",
|
||||
RdpFileSystemEventHandler.class,
|
||||
new String[]{"type", "event"},
|
||||
RdpFileSystemEventRequest.class),
|
||||
|
||||
;
|
||||
|
||||
|
||||
@@ -69,6 +69,13 @@ public enum OutputProtocolEnum {
|
||||
*/
|
||||
RESIZE("rs", "${type}|${width}|${height}"),
|
||||
|
||||
// ----------------------- guacd ----------------------
|
||||
|
||||
/**
|
||||
* guacd 指令
|
||||
*/
|
||||
GUACD_INSTRUCTION("gi", "${type}|${instruction}"),
|
||||
|
||||
// ----------------------- ssh ----------------------
|
||||
|
||||
/**
|
||||
@@ -143,13 +150,6 @@ public enum OutputProtocolEnum {
|
||||
*/
|
||||
SFTP_SET_CONTENT("sc", "${type}|${result}|${msg}|${token}"),
|
||||
|
||||
// ----------------------- guacd ----------------------
|
||||
|
||||
/**
|
||||
* guacd 指令
|
||||
*/
|
||||
GUACD_INSTRUCTION("gi", "${type}|${instruction}"),
|
||||
|
||||
;
|
||||
|
||||
private final String type;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.handler.terminal.handler;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.module.terminal.define.operator.TerminalOperatorType;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.request.RdpFileSystemEventRequest;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.transport.RdpFileSystemEvent;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.sender.IGuacdTerminalSender;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* rdp 文件系统事件 处理器
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/2/19 11:13
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RdpFileSystemEventHandler extends AbstractTerminalHandler<IGuacdTerminalSender, RdpFileSystemEventRequest> {
|
||||
|
||||
@Override
|
||||
public void handle(TerminalChannelProps props, IGuacdTerminalSender sender, RdpFileSystemEventRequest payload) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
String sessionId = props.getId();
|
||||
// 获取会话
|
||||
RdpFileSystemEvent fsEvent = JSON.parseObject(payload.getEvent(), RdpFileSystemEvent.class);
|
||||
String event = fsEvent.getEvent();
|
||||
String path = fsEvent.getPath();
|
||||
log.info("RdpFileSystemEventHandler-handle start sessionId: {}, event: {}, path: {}", sessionId, event, path);
|
||||
String operatorType;
|
||||
if (TerminalOperatorType.RDP_UPLOAD.equals(event)) {
|
||||
// 上传文件
|
||||
operatorType = TerminalOperatorType.RDP_UPLOAD;
|
||||
} else if (TerminalOperatorType.RDP_DOWNLOAD.equals(event)) {
|
||||
// 下载文件
|
||||
operatorType = TerminalOperatorType.RDP_DOWNLOAD;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// 保存操作日志
|
||||
Map<String, Object> extra = Maps.newMap();
|
||||
extra.put(OperatorLogs.PATH, path);
|
||||
this.saveOperatorLog(props, extra, operatorType, startTime, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.handler.terminal.model.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalBasePayload;
|
||||
|
||||
/**
|
||||
* rdp 文件系统事件请求
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/2/6 13:31
|
||||
*/
|
||||
@Data
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class RdpFileSystemEventRequest extends TerminalBasePayload {
|
||||
|
||||
/**
|
||||
* 事件
|
||||
*/
|
||||
private String event;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.terminal.handler.terminal.model.transport;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* RDP 文件系统事件
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2025/6/30 22:33
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RdpFileSystemEvent {
|
||||
|
||||
/**
|
||||
* 事件
|
||||
*/
|
||||
private String event;
|
||||
|
||||
/**
|
||||
* 文件路径
|
||||
*/
|
||||
private String path;
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
package org.dromara.visor.module.terminal.handler.transfer.session;
|
||||
|
||||
import cn.orionsec.kit.lang.exception.argument.InvalidArgumentException;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.net.host.SessionStore;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpExecutor;
|
||||
@@ -33,14 +34,18 @@ import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.constant.FieldConst;
|
||||
import org.dromara.visor.common.session.config.SshConnectConfig;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorLogModel;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.record.TerminalAsyncSaver;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.utils.TerminalUtils;
|
||||
import org.dromara.visor.module.terminal.handler.transfer.enums.TransferReceiver;
|
||||
import org.dromara.visor.module.terminal.handler.transfer.model.TransferOperatorRequest;
|
||||
import org.dromara.visor.module.terminal.handler.transfer.utils.TransferUtils;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 主机传输会话实现
|
||||
@@ -149,10 +154,19 @@ public abstract class TransferSession implements ITransferSession {
|
||||
* @param paths paths
|
||||
*/
|
||||
protected void saveOperatorLog(Long logId, String type, List<String> paths) {
|
||||
TerminalChannelProps props = WebSockets.getAttr(channel, FieldConst.PROPS);
|
||||
String path = String.join(Const.LF, paths);
|
||||
int count = paths.size();
|
||||
// 获取操作日志
|
||||
OperatorLogModel model = TransferUtils.getOperatorLogModel(type, path, count, connectConfig, WebSockets.getAttr(channel, FieldConst.PROPS));
|
||||
Map<String, Object> extra = Maps.newMap();
|
||||
extra.put(OperatorLogs.PATH, path);
|
||||
extra.put(OperatorLogs.COUNT, count);
|
||||
extra.put(OperatorLogs.HOST_ID, connectConfig.getHostId());
|
||||
extra.put(OperatorLogs.HOST_NAME, connectConfig.getHostName());
|
||||
extra.put(OperatorLogs.ADDRESS, connectConfig.getHostAddress());
|
||||
OperatorLogModel model = TerminalUtils.getOperatorLogModel(props, extra, type, System.currentTimeMillis(), null);
|
||||
// 保存操作日志
|
||||
TerminalAsyncSaver.saveOperatorLog(model);
|
||||
// 保存操作日志
|
||||
TerminalAsyncSaver.saveOperatorLog(model);
|
||||
}
|
||||
|
||||
@@ -23,21 +23,14 @@
|
||||
package org.dromara.visor.module.terminal.handler.transfer.utils;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.apache.catalina.connector.ClientAbortException;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.session.config.BaseConnectConfig;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorLogModel;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
|
||||
import org.dromara.visor.module.terminal.handler.terminal.utils.TerminalUtils;
|
||||
import org.dromara.visor.module.terminal.handler.transfer.enums.TransferReceiver;
|
||||
import org.dromara.visor.module.terminal.handler.transfer.model.TransferOperatorResponse;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -52,30 +45,6 @@ public class TransferUtils {
|
||||
private TransferUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取传输操作日志
|
||||
*
|
||||
* @param type type
|
||||
* @param path path
|
||||
* @param count count
|
||||
* @param config config
|
||||
* @param props props
|
||||
*/
|
||||
public static OperatorLogModel getOperatorLogModel(String type,
|
||||
String path, Integer count,
|
||||
BaseConnectConfig config,
|
||||
TerminalChannelProps props) {
|
||||
// 设置参数
|
||||
Map<String, Object> extra = Maps.newMap();
|
||||
extra.put(OperatorLogs.PATH, path);
|
||||
extra.put(OperatorLogs.COUNT, count);
|
||||
extra.put(OperatorLogs.HOST_ID, config.getHostId());
|
||||
extra.put(OperatorLogs.HOST_NAME, config.getHostName());
|
||||
extra.put(OperatorLogs.ADDRESS, config.getHostAddress());
|
||||
// 获取操作日志
|
||||
return TerminalUtils.getOperatorLogModel(props, extra, type, System.currentTimeMillis(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
|
||||
@@ -275,7 +275,11 @@
|
||||
|
||||
// 上传文件
|
||||
const uploadFile = () => {
|
||||
transferManager.rdp.addUpload(props.session, fileList.value[0].file as File);
|
||||
const file = fileList.value[0].file as File;
|
||||
// 记录事件
|
||||
props.session.onFileSystemEvent({ event: 'terminal:rdp-upload', path: file.name });
|
||||
// 上传文件
|
||||
transferManager.rdp.addUpload(props.session, file);
|
||||
fileList.value = [];
|
||||
};
|
||||
|
||||
|
||||
@@ -175,6 +175,8 @@ export interface IRdpSession extends IGuacdSession {
|
||||
|
||||
// 初始化
|
||||
init: (config: GuacdInitConfig) => Promise<void>;
|
||||
// 文件系统事件
|
||||
onFileSystemEvent: (event: Record<string, any>) => void;
|
||||
// 发送键
|
||||
sendKeys: (keys: Array<number>) => void;
|
||||
// 粘贴
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { OutputPayload } from '@/views/terminal/types/protocol';
|
||||
import { InputProtocol } from '@/views/terminal/types/protocol';
|
||||
import { TerminalMessages, fitDisplayValue, TerminalCloseCode } from '@/views/terminal/types/const';
|
||||
import { screenshot } from '@/views/terminal/types/utils';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import Guacamole from 'guacamole-common-js';
|
||||
import BaseSession from './base-session';
|
||||
@@ -105,6 +106,13 @@ export default class RdpSession extends BaseSession<GuacdReactiveSessionStatus,
|
||||
};
|
||||
// 下载文件回调
|
||||
this.client.onfile = (stream, mimetype, filename) => {
|
||||
if (!this.isWriteable()) {
|
||||
Message.error('无写入权限');
|
||||
return;
|
||||
}
|
||||
// 记录事件
|
||||
this.onFileSystemEvent({ event: 'terminal:rdp-download', path: filename });
|
||||
// 下载文件
|
||||
useTerminalStore().transferManager.rdp.addDownload(this, stream, mimetype, filename);
|
||||
};
|
||||
}
|
||||
@@ -160,6 +168,11 @@ export default class RdpSession extends BaseSession<GuacdReactiveSessionStatus,
|
||||
}
|
||||
}
|
||||
|
||||
// 文件系统事件
|
||||
onFileSystemEvent(event: Record<string, any>): void {
|
||||
this.channel.send(InputProtocol.RDP_FILE_SYSTEM_EVENT, { event: JSON.stringify(event) });
|
||||
}
|
||||
|
||||
// 发送键
|
||||
sendKeys(keys: Array<number>): void {
|
||||
if (!this.isWriteable()) {
|
||||
|
||||
@@ -45,6 +45,11 @@ export const InputProtocol = {
|
||||
type: 'rs',
|
||||
template: ['type', 'width', 'height']
|
||||
},
|
||||
// guacd 指令
|
||||
GUACD_INSTRUCTION: {
|
||||
type: 'gi',
|
||||
template: ['type', 'instruction']
|
||||
},
|
||||
// SSH 输入
|
||||
SSH_INPUT: {
|
||||
type: 'i',
|
||||
@@ -95,11 +100,11 @@ export const InputProtocol = {
|
||||
type: 'sc',
|
||||
template: ['type', 'path']
|
||||
},
|
||||
// guacd 指令
|
||||
GUACD_INSTRUCTION: {
|
||||
type: 'gi',
|
||||
template: ['type', 'instruction']
|
||||
}
|
||||
// RDP 文件系统事件
|
||||
RDP_FILE_SYSTEM_EVENT: {
|
||||
type: 'fse',
|
||||
template: ['type', 'event']
|
||||
},
|
||||
};
|
||||
|
||||
// 输出协议
|
||||
@@ -140,6 +145,12 @@ export const OutputProtocol = {
|
||||
template: ['type'],
|
||||
processMethod: 'processPong'
|
||||
},
|
||||
// guacd 指令
|
||||
GUACD_INSTRUCTION: {
|
||||
type: 'gi',
|
||||
template: ['type', 'instruction'],
|
||||
processMethod: 'processInstruction'
|
||||
},
|
||||
// SSH 输出
|
||||
SSH_OUTPUT: {
|
||||
type: 'o',
|
||||
@@ -200,12 +211,6 @@ export const OutputProtocol = {
|
||||
template: ['type', 'result', 'msg', 'token'],
|
||||
processMethod: 'processSftpSetContent'
|
||||
},
|
||||
// guacd 指令
|
||||
GUACD_INSTRUCTION: {
|
||||
type: 'gi',
|
||||
template: ['type', 'instruction'],
|
||||
processMethod: 'processInstruction'
|
||||
}
|
||||
};
|
||||
|
||||
// 解析参数
|
||||
|
||||
Reference in New Issue
Block a user