리눅스접근

2024. 11. 19. 15:14카테고리 없음

반응형

리눅스 명령어를 실행해주는 Spring Boot 에이전트를 개발하려면 다음과 같은 단계를 따라가면 됩니다.

1. 프로젝트 초기 설정

1. Spring Boot 프로젝트를 생성하세요.
• 필요한 의존성:
• Spring Web
• Spring Boot Actuator (옵션)
• Spring Security (옵션: 보안 필요 시)
2. Gradle 또는 Maven으로 의존성을 추가합니다.

2. 커맨드 실행 로직 구현

Java에서는 ProcessBuilder 또는 Runtime.getRuntime()를 사용하여 명령어를 실행할 수 있습니다.

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class CommandExecutor {
    public String executeCommand(String command) throws Exception {
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command("bash", "-c", command);

        Process process = processBuilder.start();
        StringBuilder output = new StringBuilder();

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line).append("\n");
            }
        }

        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new RuntimeException("Command failed with exit code " + exitCode);
        }

        return output.toString();
    }
}

3. REST API 개발

Spring Boot를 사용해 REST API로 요청을 받을 수 있도록 컨트롤러를 구현합니다.

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/command")
public class CommandController {

    private final CommandExecutor commandExecutor;

    public CommandController() {
        this.commandExecutor = new CommandExecutor();
    }

    @PostMapping("/execute")
    public String executeCommand(@RequestBody CommandRequest request) {
        try {
            return commandExecutor.executeCommand(request.getCommand());
        } catch (Exception e) {
            return "Error: " + e.getMessage();
        }
    }
}

class CommandRequest {
    private String command;

    // Getter and Setter
    public String getCommand() {
        return command;
    }

    public void setCommand(String command) {
        this.command = command;
    }
}

4. 보안 및 검증

(1) 명령어 제한

실행 가능한 명령어를 화이트리스트로 제한합니다.

import java.util.List;

public class CommandValidator {
    private static final List<String> ALLOWED_COMMANDS = List.of("ls", "pwd", "whoami");

    public static boolean isValidCommand(String command) {
        return ALLOWED_COMMANDS.stream().anyMatch(command::startsWith);
    }
}

CommandController에서 사용:

if (!CommandValidator.isValidCommand(request.getCommand())) {
    return "Error: Command not allowed.";
}

(2) Spring Security 추가

관리자만 API에 접근 가능하도록 Spring Security를 설정합니다.

5. 테스트

1. 로컬에서 실행 테스트
• POST 요청으로 명령어를 전달하고 결과를 확인합니다.

curl -X POST http://localhost:8080/api/command/execute \
     -H "Content-Type: application/json" \
     -d '{"command": "ls"}'


2. 예외 상황 테스트
• 허용되지 않은 명령어를 실행하거나 잘못된 명령어를 테스트합니다.

6. 운영 환경 배포

1. 리눅스 환경에서 실행
• systemctl로 Spring Boot 애플리케이션을 서비스로 등록합니다.
2. 로그 관리
• 실행 결과를 로깅하거나 실패한 명령어 기록을 남깁니다.
3. 보안 강화
• IP 화이트리스트 설정 또는 인증 토큰 적용.

추가 고려사항

• 멀티스레드 처리: 동시에 여러 요청이 들어올 경우 동시 실행을 고려하세요.
• 실행 시간 제한: 특정 명령어가 너무 오래 실행되지 않도록 타임아웃을 설정하세요.
• 명령어 입력 데이터 검증: 명령어에 악성 입력(예: rm -rf /)이 들어가지 않도록 철저히 검증하세요.

필요하면 더 구체적인 구현을 도와줄게요!




네, 맞습니다! Spring Boot 에이전트에서 명령어를 실행하기 때문에 디렉토리 생성이나 쉘 스크립트를 실행하는 등의 작업을 충분히 수행할 수 있습니다. 하지만 이런 작업은 보안 및 입력 검증을 철저히 해야 합니다.

아래는 디렉토리 생성과 쉘 스크립트 실행을 예제로 설명하겠습니다.

1. 디렉토리 생성

mkdir 명령어를 통해 디렉토리를 생성할 수 있습니다.

요청 예시:

{
    "command": "mkdir /tmp/new_directory"
}

실행 로직:

@PostMapping("/execute")
public String executeCommand(@RequestBody CommandRequest request) {
    try {
        String result = commandExecutor.executeCommand(request.getCommand());
        return "Success: " + result;
    } catch (Exception e) {
        return "Error: " + e.getMessage();
    }
}

테스트:

curl -X POST http://localhost:8080/api/command/execute \
    -H "Content-Type: application/json" \
    -d '{"command": "mkdir /tmp/new_directory"}'

2. 쉘 스크립트 실행

쉘 스크립트 파일을 실행하려면 bash script.sh 또는 ./script.sh 명령어를 사용할 수 있습니다.

요청 예시:

{
    "command": "bash /path/to/your/script.sh"
}

실행 로직:

스크립트는 bash 명령어를 통해 실행됩니다. 스크립트 파일에 실행 권한이 필요하니, 권한 설정도 신경 써야 합니다.

@PostMapping("/execute-script")
public String executeShellScript(@RequestBody CommandRequest request) {
    try {
        return commandExecutor.executeCommand(request.getCommand());
    } catch (Exception e) {
        return "Error: " + e.getMessage();
    }
}

테스트:

curl -X POST http://localhost:8080/api/command/execute \
    -H "Content-Type: application/json" \
    -d '{"command": "bash /path/to/your/script.sh"}'

3. 권한 및 검증

• 쉘 스크립트 권한 설정:
• 실행 전에 스크립트 파일에 실행 권한을 부여해야 합니다.

chmod +x /path/to/your/script.sh


• 명령어 제한:
• mkdir, bash와 같은 특정 명령어만 허용하도록 검증 로직을 추가하세요.

명령어 검증 코드:

private static final List<String> ALLOWED_COMMANDS = List.of("mkdir", "bash");

public static boolean isValidCommand(String command) {
    return ALLOWED_COMMANDS.stream().anyMatch(command::startsWith);
}

4. 추가적인 보안 고려사항

1. 명령어 인젝션 방지:
• 사용자 입력을 검증하여 악의적인 명령어(rm -rf /, ;, && 포함)를 차단하세요.
2. 작업 디렉토리 제한:
• 특정 디렉토리(/tmp/ 또는 /opt/agent/)에서만 작업하도록 제한하세요.
3. 실행 결과 로깅:
• 실행 결과와 에러를 로그로 기록하여 문제 상황을 추적합니다.

5. 실행 예시

디렉토리 생성:

curl -X POST http://localhost:8080/api/command/execute \
    -H "Content-Type: application/json" \
    -d '{"command": "mkdir /tmp/test_directory"}'

쉘 스크립트 실행:

curl -X POST http://localhost:8080/api/command/execute \
    -H "Content-Type: application/json" \
    -d '{"command": "bash /tmp/test_script.sh"}'

이 에이전트를 이용하면 디렉토리 생성, 파일 복사, 쉘 스크립트 실행 등 다양한 작업을 수행할 수 있습니다. 추가로 구현하고 싶은 동작이나 보안 설정이 있다면 말씀해주세요!





맞습니다! 일반적으로 Spring 애플리케이션에서는 비즈니스 로직은 서비스 레이어에서 처리하고, 컨트롤러는 요청을 받아 서비스 레이어에 위임하는 방식으로 설계하는 것이 더 좋은 구조입니다. 이렇게 하면 레이어 분리가 명확해지고, 유지보수가 쉬워집니다.

아래는 서비스 레이어를 사용하는 구조로 개선한 예제입니다.

1. 레이어 설계

• Controller: 클라이언트 요청을 받아 서비스 레이어로 전달.
• Service: 명령어 실행 로직을 처리.
• CommandExecutor: 실제 명령어 실행 로직을 담당하는 유틸리티 클래스.

2. Controller 코드

import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;

@RestController
@RequestMapping("/api/command")
public class CommandController {

    private final CommandService commandService;

    @Autowired
    public CommandController(CommandService commandService) {
        this.commandService = commandService;
    }

    // 디렉토리 생성 요청
    @PostMapping("/mkdir")
    public String createDirectory(@RequestBody DirectoryRequest request) {
        try {
            commandService.createDirectory(request.getId());
            return "Directory created: /tmp/" + request.getId();
        } catch (Exception e) {
            return "Error: " + e.getMessage();
        }
    }

    // 스크립트 실행 요청
    @PostMapping("/run-script")
    public String runScript(@RequestBody ScriptRequest request) {
        try {
            commandService.runScript(request.getScriptName());
            return "Script executed: /scripts/" + request.getScriptName();
        } catch (Exception e) {
            return "Error: " + e.getMessage();
        }
    }
}

3. Service 코드

CommandService는 비즈니스 로직을 처리하며, 명령어를 실행하기 위해 CommandExecutor를 호출합니다.

import org.springframework.stereotype.Service;

@Service
public class CommandService {

    private final CommandExecutor commandExecutor;

    public CommandService() {
        this.commandExecutor = new CommandExecutor();
    }

    // 디렉토리 생성 비즈니스 로직
    public void createDirectory(String id) throws Exception {
        if (id == null || id.isEmpty()) {
            throw new IllegalArgumentException("Directory ID cannot be null or empty");
        }

        String directoryPath = "/tmp/" + id; // 고정된 디렉토리 경로
        String command = "mkdir -p " + directoryPath;
        commandExecutor.executeCommand(command);
    }

    // 스크립트 실행 비즈니스 로직
    public void runScript(String scriptName) throws Exception {
        if (scriptName == null || scriptName.isEmpty()) {
            throw new IllegalArgumentException("Script name cannot be null or empty");
        }

        // 허용된 스크립트만 실행
        if (!isValidScript(scriptName)) {
            throw new SecurityException("Unauthorized script: " + scriptName);
        }

        String scriptPath = "/scripts/" + scriptName; // 고정된 스크립트 경로
        String command = "bash " + scriptPath;
        commandExecutor.executeCommand(command);
    }

    // 스크립트 검증 로직
    private boolean isValidScript(String scriptName) {
        return List.of("example_script.sh", "cleanup.sh").contains(scriptName);
    }
}

4. CommandExecutor 코드

명령어를 실제로 실행하는 로직은 기존과 동일하게 CommandExecutor에서 처리합니다.

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class CommandExecutor {

    public void executeCommand(String command) throws Exception {
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command("bash", "-c", command);

        Process process = processBuilder.start();

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line); // 명령어 출력 로그
            }
        }

        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new RuntimeException("Command failed with exit code " + exitCode);
        }
    }
}

5. 요청 DTO

기존과 동일하게 요청 데이터를 처리하는 DTO 클래스입니다.

class DirectoryRequest {
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

class ScriptRequest {
    private String scriptName;

    public String getScriptName() {
        return scriptName;
    }

    public void setScriptName(String scriptName) {
        this.scriptName = scriptName;
    }
}

6. 이 구조의 장점

1. 레이어 분리:
• Controller는 요청 처리에만 집중하고, 서비스는 비즈니스 로직을 담당하므로 역할이 명확히 분리됩니다.
2. 유지보수성 향상:
• 서비스 레이어에서 로직을 변경해도 컨트롤러 코드에는 영향을 미치지 않습니다.
• 테스트 및 로깅이 더 간단해집니다.
3. 확장성:
• 추가 비즈니스 로직이 필요할 경우 서비스 레이어에서 처리하면 됩니다.
• 예를 들어, 디렉토리 생성 전에 권한 체크를 추가하거나, 실행 결과를 데이터베이스에 저장하는 기능을 쉽게 확장할 수 있습니다.

7. 요청 테스트

(1) 디렉토리 생성 요청

Request URL:
POST http://localhost:8080/api/command/mkdir

Request Body:

{
    "id": "test_directory"
}

결과:
/tmp/test_directory 디렉토리가 생성됩니다.

(2) 쉘 스크립트 실행 요청

Request URL:
POST http://localhost:8080/api/command/run-script

Request Body:

{
    "scriptName": "example_script.sh"
}

결과:
/scripts/example_script.sh 스크립트가 실행됩니다.

이 구조를 기반으로 설계하면 확장성과 유지보수성을 모두 확보할 수 있습니다. 추가적으로 보안 설정이나 로그 저장이 필요하면 알려주세요!

반응형