🔨 记录 RDP 传输日志.

This commit is contained in:
lijiahangmax
2025-06-30 23:23:53 +08:00
parent 4468a429dd
commit fcec6579d7
11 changed files with 255 additions and 60 deletions

View File

@@ -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),
;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}
/**
* 发送消息
*

View File

@@ -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 = [];
};

View File

@@ -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;
// 粘贴

View File

@@ -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()) {

View File

@@ -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'
}
};
// 解析参数