Merge branch 'feature/SUPPORT-8841_pass_protect_files_for_dev' into develop
# Conflicts: # README.md
This commit is contained in:
commit
f9204bf4f7
8 changed files with 90 additions and 107 deletions
34
README.md
34
README.md
|
|
@ -22,16 +22,36 @@
|
||||||
## **Конфигурация и запуск**
|
## **Конфигурация и запуск**
|
||||||
|
|
||||||
### **1. Конфигурация параметров для сервиса**
|
### **1. Конфигурация параметров для сервиса**
|
||||||
Для корректного запуска сервиса добавьте следующие параметры в файл `application.properties`:
|
Для корректного запуска сервиса добавьте следующие параметры в файл `application.yaml`:
|
||||||
```properties
|
```yaml
|
||||||
file.upload.directory=/tmp/uploaded_files # Путь для хранения временных файлов
|
file:
|
||||||
spring.servlet.multipart.max-file-size=10MB # Максимальный размер загружаемого файла
|
upload:
|
||||||
spring.servlet.multipart.max-request-size=10MB # Максимальный размер запроса
|
directory: '/tmp/uploaded_files' # Путь для хранения временных файлов
|
||||||
server.port=8080 # Порт, на котором работает сервис
|
spring:
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 10MB # Максимальный размер загружаемого файла
|
||||||
|
max-request-size: 10MB # Максимальный размер запроса
|
||||||
|
server:
|
||||||
|
port: 8080 # Порт, на котором работает сервис
|
||||||
|
```
|
||||||
|
Если KESL использует русскую локализацию, то следует в файл `application.yaml` добавить следующий параметр:
|
||||||
|
```yaml
|
||||||
|
password:
|
||||||
|
protected:
|
||||||
|
result:
|
||||||
|
name: 'Объекты, защищенные паролем'
|
||||||
|
```
|
||||||
|
Для английской локализации:
|
||||||
|
```yaml
|
||||||
|
password:
|
||||||
|
protected:
|
||||||
|
result:
|
||||||
|
name: 'Password-protected objects'
|
||||||
```
|
```
|
||||||
### **2. Запуск JAR-файла с конфигурационным файлом**
|
### **2. Запуск JAR-файла с конфигурационным файлом**
|
||||||
```bash
|
```bash
|
||||||
java -jar app.jar --spring.config.location=file:/path/to/your/application.properties
|
java -jar app.jar --spring.config.location=file:/path/to/your/application.yaml -Dfile.encoding=UTF8
|
||||||
```
|
```
|
||||||
|
|
||||||
Ссылка на av-service задаётся в перекладчике (сервис ervu-lkrp-av) в параметре AV_REST_ADDRESS. Пример - AV_REST_ADDRESS=http://10.10.10.10:8080/av-wrapper/scan-file
|
Ссылка на av-service задаётся в перекладчике (сервис ervu-lkrp-av) в параметре AV_REST_ADDRESS. Пример - AV_REST_ADDRESS=http://10.10.10.10:8080/av-wrapper/scan-file
|
||||||
|
|
@ -8,7 +8,7 @@ Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
User=root
|
User=root
|
||||||
WorkingDirectory=/opt/av-service
|
WorkingDirectory=/opt/av-service
|
||||||
ExecStart=/usr/bin/java -jar av-service.jar
|
ExecStart=/usr/bin/java -jar av-service.jar -Dfile.encoding=UTF8
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
||||||
6
pom.xml
6
pom.xml
|
|
@ -22,6 +22,12 @@
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.zeroturnaround</groupId>
|
||||||
|
<artifactId>zt-exec</artifactId>
|
||||||
|
<version>1.12</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import ru.micord.av.service.model.ScanResult;
|
|
||||||
import ru.micord.av.service.service.FileScanService;
|
import ru.micord.av.service.service.FileScanService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,11 +31,10 @@ public class FileScanController {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ScanResult result = fileScanService.scanFile(file);
|
int result = fileScanService.scanFile(file);
|
||||||
LOGGER.info("Scan result for file {}: status - {}, verdicts - {}",
|
LOGGER.info("Scan result for file {}: exitCode - {}",
|
||||||
file.getOriginalFilename(),
|
file.getOriginalFilename(),
|
||||||
result.status(),
|
result);
|
||||||
String.join(", ", result.verdicts()));
|
|
||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(result);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package ru.micord.av.service.model;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Adel Kalimullin
|
|
||||||
*/
|
|
||||||
public record ScanResult(String completed, String created, Integer progress,
|
|
||||||
Map<String, Scan> scan_result, String status, String[] verdicts) {
|
|
||||||
public record Scan(String started, String stopped, Threat[] threats, String verdict) {
|
|
||||||
public static final String VERDICT_CLEAN = "clean";
|
|
||||||
public static final String VERDICT_INFECTED = "infected";
|
|
||||||
|
|
||||||
public record Threat(String name, String object) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,23 +2,20 @@ package ru.micord.av.service.service;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.LocalDateTime;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import org.zeroturnaround.exec.ProcessExecutor;
|
||||||
|
import org.zeroturnaround.exec.ProcessResult;
|
||||||
|
import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;
|
||||||
import ru.micord.av.service.exception.AvException;
|
import ru.micord.av.service.exception.AvException;
|
||||||
import ru.micord.av.service.model.ScanResult;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adel Kalimullin
|
* @author Adel Kalimullin
|
||||||
|
|
@ -30,21 +27,16 @@ public class FileScanService {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(FileScanService.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(FileScanService.class);
|
||||||
@Value("${file.upload.directory}")
|
@Value("${file.upload.directory}")
|
||||||
private String uploadDirectory;
|
private String uploadDirectory;
|
||||||
|
@Value("${password.protected.result.name:Объекты, защищенные паролем}")
|
||||||
|
private String passwordProtectedResultName;
|
||||||
|
|
||||||
public ScanResult scanFile(MultipartFile file) {
|
public int scanFile(MultipartFile file) {
|
||||||
File tempFile = null;
|
File tempFile = null;
|
||||||
LocalDateTime startTime;
|
|
||||||
LocalDateTime stopTime;
|
|
||||||
try {
|
try {
|
||||||
tempFile = saveFile(file);
|
tempFile = saveFile(file);
|
||||||
startTime = LocalDateTime.now();
|
return runKeslScan(tempFile, file.getOriginalFilename());
|
||||||
String rawResult = runKeslScan(tempFile);
|
|
||||||
stopTime = LocalDateTime.now();
|
|
||||||
return parseKeslResponse(rawResult, startTime, stopTime, tempFile,
|
|
||||||
file.getOriginalFilename()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (IOException | InterruptedException | TimeoutException e) {
|
||||||
throw new AvException("Error scanning file: " + file.getOriginalFilename(), e);
|
throw new AvException("Error scanning file: " + file.getOriginalFilename(), e);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
|
@ -61,72 +53,49 @@ public class FileScanService {
|
||||||
|
|
||||||
private File saveFile(MultipartFile file) throws IOException {
|
private File saveFile(MultipartFile file) throws IOException {
|
||||||
Path uploadPath = Paths.get(uploadDirectory);
|
Path uploadPath = Paths.get(uploadDirectory);
|
||||||
if (!Files.exists(uploadPath)) {
|
Files.createDirectories(uploadPath);
|
||||||
Files.createDirectories(uploadPath);
|
Path tempFilePath = Files.createTempFile(uploadPath, "kesl-upload-", ".tmp");
|
||||||
}
|
File tempFile = tempFilePath.toFile();
|
||||||
Path tempFile = Files.createTempFile(uploadPath, "kesl-upload-", ".tmp");
|
file.transferTo(tempFile);
|
||||||
file.transferTo(tempFile.toFile());
|
return tempFile;
|
||||||
return tempFile.toFile();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String runKeslScan(File file) throws IOException, InterruptedException {
|
private int runKeslScan(File file, String originalFileName)
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder(KESL_CONTROL, KESL_SCAN,
|
throws IOException, InterruptedException, TimeoutException {
|
||||||
file.getAbsolutePath()
|
ProcessResult processResult = new ProcessExecutor()
|
||||||
);
|
.command(KESL_CONTROL, KESL_SCAN, file.getAbsolutePath())
|
||||||
processBuilder.redirectErrorStream(true);
|
.redirectOutput(Slf4jStream.of(getClass()).asDebug())
|
||||||
Process process = processBuilder.start();
|
.readOutput(true)
|
||||||
try (InputStream inputStream = process.getInputStream()) {
|
.execute();
|
||||||
String result = new String(inputStream.readAllBytes());
|
String processOutput = processResult.outputUTF8();
|
||||||
int exitCode = process.waitFor();
|
int exitCode = processResult.getExitValue();
|
||||||
if (exitCode != 0 && exitCode != 72) {
|
|
||||||
throw new AvException("KESL error, exit code: " + exitCode);
|
if (exitCode != 0 && exitCode != 72) {
|
||||||
}
|
throw new AvException(
|
||||||
return result;
|
"KESL error scanning file: " + originalFileName + ", exit code: " + exitCode);
|
||||||
}
|
}
|
||||||
|
checkScanResult(processOutput, originalFileName);
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScanResult parseKeslResponse(String rawResult, LocalDateTime startTime,
|
private void checkScanResult(String result, String originalFileName) {
|
||||||
LocalDateTime stopTime, File file, String originalFilename) {
|
if (!result.contains(passwordProtectedResultName)) {
|
||||||
Map<String, ScanResult.Scan> scanResults = new HashMap<>();
|
throw new AvException(String.format(
|
||||||
List<String> verdicts = new ArrayList<>();
|
"File scan result doesn't contains \"%s\", "
|
||||||
List<ScanResult.Scan.Threat> threats = new ArrayList<>();
|
+ "please check property \"password.protected.result.name\" is correct",
|
||||||
String verdict = null;
|
passwordProtectedResultName
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
for (String line : rawResult.split("\n")) {
|
for (String line : result.split("\n")) {
|
||||||
if (line.startsWith("Infected objects")) {
|
String[] lineParts = line.split(":");
|
||||||
verdict = extractInteger(line) > 0
|
if (lineParts.length > 1) {
|
||||||
? ScanResult.Scan.VERDICT_INFECTED
|
if (lineParts[0].startsWith(passwordProtectedResultName)
|
||||||
: ScanResult.Scan.VERDICT_CLEAN;
|
&& Integer.parseInt(lineParts[1].trim()) > 0) {
|
||||||
if (ScanResult.Scan.VERDICT_INFECTED.equals(verdict))
|
throw new AvException("Detected password-protected file: " + originalFileName);
|
||||||
threats.add(new ScanResult.Scan.Threat("", file.getAbsolutePath()));
|
}
|
||||||
}
|
|
||||||
if (line.startsWith("Scan errors") && extractInteger(line) > 0) {
|
|
||||||
verdict = "error";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (verdict != null) {
|
|
||||||
scanResults.put(originalFilename,
|
|
||||||
new ScanResult.Scan(startTime.toString(), stopTime.toString(),
|
|
||||||
threats.toArray(new ScanResult.Scan.Threat[0]), verdict
|
|
||||||
)
|
|
||||||
);
|
|
||||||
verdicts.add(verdict);
|
|
||||||
}
|
|
||||||
return new ScanResult(startTime.toString(), stopTime.toString(), 100, scanResults, "completed",
|
|
||||||
verdicts.toArray(new String[0])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int extractInteger(String line) {
|
|
||||||
String[] parts = line.split(":");
|
|
||||||
if (parts.length > 1) {
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(parts[1].trim());
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
file.upload.directory = /tmp/uploaded_files
|
|
||||||
8
src/main/resources/application.yaml
Normal file
8
src/main/resources/application.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
file:
|
||||||
|
upload:
|
||||||
|
directory: '/tmp/uploaded_files'
|
||||||
|
|
||||||
|
password:
|
||||||
|
protected:
|
||||||
|
result:
|
||||||
|
name: 'Объекты, защищенные паролем'
|
||||||
Loading…
Add table
Add a link
Reference in a new issue