feat: 请求 API 添加 token 认证;调整 tcpdump 执行逻辑;新增 execShellCmd 接口
This commit is contained in:
1
pom.xml
1
pom.xml
@@ -71,6 +71,7 @@
|
|||||||
<excludes>
|
<excludes>
|
||||||
<exclude>**/application-*.yml</exclude>
|
<exclude>**/application-*.yml</exclude>
|
||||||
<exclude>**/logback-spring.xml</exclude>
|
<exclude>**/logback-spring.xml</exclude>
|
||||||
|
<exclude>**/token.auth</exclude>
|
||||||
<!--lib 下的依赖不加入 jar,部署时放到 jar 文件同级即可-->
|
<!--lib 下的依赖不加入 jar,部署时放到 jar 文件同级即可-->
|
||||||
<exclude>lib/*.*</exclude>
|
<exclude>lib/*.*</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
|
|||||||
@@ -127,6 +127,11 @@ public class APIController {
|
|||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pcap")
|
||||||
|
public R listTcpdump() {
|
||||||
|
return R.ok().putData("records", adbUtil.listTcpdump());
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/pcap")
|
@PostMapping("/pcap")
|
||||||
public R startTcpdump(@RequestParam(required = false, defaultValue = "") String packageName) {
|
public R startTcpdump(@RequestParam(required = false, defaultValue = "") String packageName) {
|
||||||
AdbUtil.CommandResult result = adbUtil.startTcpdump(packageName);
|
AdbUtil.CommandResult result = adbUtil.startTcpdump(packageName);
|
||||||
@@ -149,7 +154,10 @@ public class APIController {
|
|||||||
// response pcap file
|
// response pcap file
|
||||||
File tempFile = T.FileUtil.file(Constant.TEMP_PATH, id + ".pcap");
|
File tempFile = T.FileUtil.file(Constant.TEMP_PATH, id + ".pcap");
|
||||||
try {
|
try {
|
||||||
String filePath = "/data/local/tmp/" + id + ".pcap";
|
String filePath = result.output();
|
||||||
|
if (T.StrUtil.isEmpty(filePath)) {
|
||||||
|
throw new APIException(RCode.NOT_EXISTS);
|
||||||
|
}
|
||||||
AdbUtil.CommandResult pulled = adbUtil.pull(filePath, tempFile.getAbsolutePath());
|
AdbUtil.CommandResult pulled = adbUtil.pull(filePath, tempFile.getAbsolutePath());
|
||||||
if (0 != pulled.exitCode()) {
|
if (0 != pulled.exitCode()) {
|
||||||
throw new APIException(pulled.output());
|
throw new APIException(pulled.output());
|
||||||
@@ -163,4 +171,15 @@ public class APIController {
|
|||||||
response.getWriter().write(T.JSONUtil.toJsonStr(R.ok().putData("id", id)));
|
response.getWriter().write(T.JSONUtil.toJsonStr(R.ok().putData("id", id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/shell")
|
||||||
|
public R execShellCmd(@RequestBody Map<String, Object> requestBody) {
|
||||||
|
String cmd = T.MapUtil.getStr(requestBody, "cmd", "");
|
||||||
|
if (T.StrUtil.isEmpty(cmd)) {
|
||||||
|
return R.error(RCode.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer timeout = T.MapUtil.getInt(requestBody, "timeout", 10);
|
||||||
|
return R.ok().putData("result", adbUtil.execShellCommand(cmd, timeout));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -482,6 +482,7 @@ public class AdbUtil {
|
|||||||
* iptables -F
|
* iptables -F
|
||||||
* iptables -X
|
* iptables -X
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
private void cleanIptables() {
|
private void cleanIptables() {
|
||||||
// Delete all rules in chain or all chains
|
// Delete all rules in chain or all chains
|
||||||
CommandExec.exec(AdbCommandBuilder.builder()
|
CommandExec.exec(AdbCommandBuilder.builder()
|
||||||
@@ -497,17 +498,47 @@ public class AdbUtil {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* list tcpdump
|
||||||
|
*/
|
||||||
|
public List<Map> listTcpdump() {
|
||||||
|
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||||||
|
.serial(this.getSerial())
|
||||||
|
.buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep capture_ | awk '{print $NF}' \""))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
List<Map> list = T.ListUtil.list(true);
|
||||||
|
|
||||||
|
String[] lines = result.split("\\n");
|
||||||
|
for (String line : lines) {
|
||||||
|
try {
|
||||||
|
String fileName = T.FileUtil.mainName(line);
|
||||||
|
String taskId = "", packageName = "";
|
||||||
|
if (fileName.contains("capture_all_")) {
|
||||||
|
taskId = fileName.replaceAll("capture_all_", "");
|
||||||
|
} else {
|
||||||
|
String[] split = fileName.split("_");
|
||||||
|
packageName = split[2];
|
||||||
|
taskId = split[split.length - 1];
|
||||||
|
}
|
||||||
|
Map<Object, Object> m = T.MapUtil.builder()
|
||||||
|
.put("id", taskId)
|
||||||
|
.put("packageName", packageName)
|
||||||
|
.build();
|
||||||
|
list.add(m);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn(e, "[listTcpdump] [get task info error] [line: {}]", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* start Tcpdump
|
* start Tcpdump
|
||||||
* iptables option
|
|
||||||
* tcpdump pcap
|
* tcpdump pcap
|
||||||
*/
|
*/
|
||||||
public CommandResult startTcpdump(String packageName) {
|
public CommandResult startTcpdump(String packageName) {
|
||||||
// clean iptables conf
|
|
||||||
this.cleanIptables();
|
|
||||||
|
|
||||||
String taskId = T.IdUtil.fastSimpleUUID();
|
String taskId = T.IdUtil.fastSimpleUUID();
|
||||||
String pcapFilePath = "/data/local/tmp/" + taskId + ".pcap";
|
|
||||||
if (T.StrUtil.isNotEmpty(packageName)) {
|
if (T.StrUtil.isNotEmpty(packageName)) {
|
||||||
log.info("[startTcpdump] [capture app package] [pkg: {}]", packageName);
|
log.info("[startTcpdump] [capture app package] [pkg: {}]", packageName);
|
||||||
String dumpsysResult = CommandExec.exec(AdbCommandBuilder.builder()
|
String dumpsysResult = CommandExec.exec(AdbCommandBuilder.builder()
|
||||||
@@ -541,12 +572,16 @@ public class AdbUtil {
|
|||||||
.build());
|
.build());
|
||||||
log.info("[startTcpdump] [iptables -L] [result: {}]", ruleList);
|
log.info("[startTcpdump] [iptables -L] [result: {}]", ruleList);
|
||||||
|
|
||||||
|
// pcap 格式:capture_{userId}_{pcakageName}_{taskId}.pcap
|
||||||
|
String pcapFilePath = "/data/local/tmp/capture_" + userId + "_" + packageName + "_" + taskId + ".pcap";
|
||||||
CommandExec.execForProcess(AdbCommandBuilder.builder()
|
CommandExec.execForProcess(AdbCommandBuilder.builder()
|
||||||
.serial(this.getSerial())
|
.serial(this.getSerial())
|
||||||
.buildShellCommand(String.format("shell tcpdump -i nflog:%s -w %s &", userId, pcapFilePath))
|
.buildShellCommand(String.format("shell tcpdump -i nflog:%s -w %s &", userId, pcapFilePath))
|
||||||
.build());
|
.build());
|
||||||
} else {
|
} else {
|
||||||
log.info("[startTcpdump] [capture all package]");
|
log.info("[startTcpdump] [capture all package]");
|
||||||
|
// pcap 格式:capture_all_{taskId}.pcap
|
||||||
|
String pcapFilePath = "/data/local/tmp/capture_all_" + taskId + ".pcap";
|
||||||
CommandExec.execForProcess(AdbCommandBuilder.builder()
|
CommandExec.execForProcess(AdbCommandBuilder.builder()
|
||||||
.serial(this.getSerial())
|
.serial(this.getSerial())
|
||||||
.buildShellCommand(String.format("shell tcpdump -w %s &", pcapFilePath))
|
.buildShellCommand(String.format("shell tcpdump -w %s &", pcapFilePath))
|
||||||
@@ -566,12 +601,39 @@ public class AdbUtil {
|
|||||||
* kill -INT {pid}
|
* kill -INT {pid}
|
||||||
*/
|
*/
|
||||||
public CommandResult stopTcpdump(String id) {
|
public CommandResult stopTcpdump(String id) {
|
||||||
|
String pcapFilePath = CommandExec.exec(AdbCommandBuilder.builder()
|
||||||
|
.serial(this.getSerial())
|
||||||
|
.buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $NF}' \"", id))
|
||||||
|
.build());
|
||||||
|
if (T.StrUtil.isNotEmpty(pcapFilePath)) {
|
||||||
|
if (!pcapFilePath.contains("capture_all_")) {
|
||||||
|
// 删除 iptables rule
|
||||||
|
String[] split = T.FileUtil.mainName(pcapFilePath).split("_");
|
||||||
|
String userId = split[1];
|
||||||
|
log.info("[stopTcpdump] [remove iptables rule] [userId: {}]", userId);
|
||||||
|
CommandExec.exec(AdbCommandBuilder.builder()
|
||||||
|
.serial(this.getSerial())
|
||||||
|
.buildShellCommand(String.format("shell iptables -D OUTPUT -m owner --uid-owner %s -j CONNMARK --set-mark %s", userId, userId))
|
||||||
|
.build());
|
||||||
|
CommandExec.exec(AdbCommandBuilder.builder()
|
||||||
|
.serial(this.getSerial())
|
||||||
|
.buildShellCommand(String.format("shell iptables -D INPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId))
|
||||||
|
.build());
|
||||||
|
CommandExec.exec(AdbCommandBuilder.builder()
|
||||||
|
.serial(this.getSerial())
|
||||||
|
.buildShellCommand(String.format("shell iptables -D OUTPUT -m connmark --mark %s -j NFLOG --nflog-group %s", userId, userId))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
String result = CommandExec.exec(AdbCommandBuilder.builder()
|
||||||
.serial(this.getSerial())
|
.serial(this.getSerial())
|
||||||
.buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $2}' | xargs kill -INT \"", id))
|
.buildShellCommand(String.format("shell \"ps -ef | grep tcpdump | grep -v grep | grep %s | awk '{print $2}' | xargs kill -INT \"", id))
|
||||||
.build());
|
.build());
|
||||||
log.info("[stopTcpdump] [id: {}] [result: {}]", id, result);
|
log.info("[stopTcpdump] [id: {}] [pcapFilePath: {}] [result: {}]", id, pcapFilePath, result);
|
||||||
return new CommandResult(T.StrUtil.isEmpty(result) ? 0 : 1, result);
|
if (T.StrUtil.isEmpty(result)) {
|
||||||
|
return new CommandResult(0, pcapFilePath);
|
||||||
|
}
|
||||||
|
return new CommandResult(1, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -585,6 +647,30 @@ public class AdbUtil {
|
|||||||
log.info("[execShellCommand] [shellCmd: {}] [result: {}]", shellCmd, result);
|
log.info("[execShellCommand] [shellCmd: {}] [result: {}]", shellCmd, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* exec shell command
|
||||||
|
*/
|
||||||
|
public String execShellCommand(String cmd, Integer timeout){
|
||||||
|
Process process = CommandExec.execForProcess(AdbCommandBuilder.builder()
|
||||||
|
.serial(this.getSerial())
|
||||||
|
.buildShellCommand("shell " + cmd)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
Future<String> future = executor.submit(() -> T.IoUtil.read(process.getInputStream(), T.CharsetUtil.CHARSET_UTF_8));
|
||||||
|
try {
|
||||||
|
String result = future.get(timeout, TimeUnit.SECONDS);
|
||||||
|
return result;
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
process.destroyForcibly();
|
||||||
|
throw new APIException(RCode.TIMEOUT);
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
throw new APIException(RCode.ERROR);
|
||||||
|
} finally {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized ExecutorService getThreadPool() {
|
private synchronized ExecutorService getThreadPool() {
|
||||||
if (threadPool == null) {
|
if (threadPool == null) {
|
||||||
threadPool = new ThreadPoolExecutor(
|
threadPool = new ThreadPoolExecutor(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public enum RCode {
|
|||||||
|
|
||||||
NOT_EXISTS(404, "No such file or directory"),
|
NOT_EXISTS(404, "No such file or directory"),
|
||||||
NOT_PERMISSION(401 , "Permission denied"),
|
NOT_PERMISSION(401 , "Permission denied"),
|
||||||
|
TIMEOUT(408, "Request Timeout"),
|
||||||
|
|
||||||
ERROR(999, "error"), // 通用错误/未知错误
|
ERROR(999, "error"), // 通用错误/未知错误
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package net.geedge.common;
|
package net.geedge.common;
|
||||||
|
|
||||||
import cn.hutool.core.io.IORuntimeException;
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
|
import cn.hutool.log.Log;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
@@ -212,4 +216,30 @@ public class T {
|
|||||||
T.IoUtil.write(response.getOutputStream(), false, data);
|
T.IoUtil.write(response.getOutputStream(), false, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class WebPathUtil {
|
||||||
|
static Log log = Log.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果已打成jar包,则返回jar包所在目录
|
||||||
|
* 如果未打成jar,则返回target所在目录
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getClassPath() {
|
||||||
|
try {
|
||||||
|
// 项目的编译文件的根目录
|
||||||
|
String path = URLDecoder.decode(System.getProperty("user.dir"), "utf-8");
|
||||||
|
log.debug("root path:{}", path);
|
||||||
|
return path;
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRootPath() {
|
||||||
|
File file = T.FileUtil.file(WebPathUtil.getClassPath());
|
||||||
|
return file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
54
src/main/java/net/geedge/common/config/TokenInterceptor.java
Normal file
54
src/main/java/net/geedge/common/config/TokenInterceptor.java
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package net.geedge.common.config;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
|
import cn.hutool.log.Log;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import net.geedge.common.T;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
public class TokenInterceptor implements WebMvcConfigurer {
|
||||||
|
private final static Log log = Log.get();
|
||||||
|
|
||||||
|
private static String tokenValue;
|
||||||
|
|
||||||
|
@Value("${device.tokenFile:config/token.auth}")
|
||||||
|
protected String tokenFile;
|
||||||
|
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() throws IORuntimeException {
|
||||||
|
tokenValue = readToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry.addInterceptor(new HandlerInterceptor() {
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
String token = request.getHeader("Authorization");
|
||||||
|
if (token == null || !token.equals(tokenValue)) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
response.getWriter().write("Unauthorized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}).addPathPatterns("/**");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readToken() throws IORuntimeException {
|
||||||
|
File tf = T.FileUtil.file(T.WebPathUtil.getRootPath(), tokenFile);
|
||||||
|
log.info("token file path: {}", tf.getAbsolutePath());
|
||||||
|
String token = T.FileUtil.readString(tf, T.CharsetUtil.UTF_8);
|
||||||
|
return T.StrUtil.trim(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,5 +8,5 @@ spring:
|
|||||||
max-request-size: 500MB
|
max-request-size: 500MB
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
logging:
|
device:
|
||||||
config:./config/logback-spring.xml
|
tokenFile: ./config/token.auth
|
||||||
|
|||||||
1
src/main/resources/config/token.auth
Normal file
1
src/main/resources/config/token.auth
Normal file
@@ -0,0 +1 @@
|
|||||||
|
2fa9a369
|
||||||
Reference in New Issue
Block a user