diff --git a/build.gradle b/build.gradle index 5cef9f6b166caa00c1f2ad460522a592a6bd8e02..b88f4542cc05422dcee3e5491eb9d93ed75f7ec7 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/com/aolda/itda/ItdaApplication.java b/src/main/java/com/aolda/itda/ItdaApplication.java index c7f4ef7eb3aae27b2c85e6297c9e74317f4ccdf6..674e65780f404587b1f8a528c8cb03087f919e60 100644 --- a/src/main/java/com/aolda/itda/ItdaApplication.java +++ b/src/main/java/com/aolda/itda/ItdaApplication.java @@ -2,8 +2,10 @@ package com.aolda.itda; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class ItdaApplication { public static void main(String[] args) { diff --git a/src/main/java/com/aolda/itda/config/AuthInterceptor.java b/src/main/java/com/aolda/itda/config/AuthInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..63f2320e18fa92cd2edb275561be760518b19cc1 --- /dev/null +++ b/src/main/java/com/aolda/itda/config/AuthInterceptor.java @@ -0,0 +1,37 @@ +package com.aolda.itda.config; + +import com.aolda.itda.exception.CustomException; +import com.aolda.itda.exception.ErrorCode; +import com.aolda.itda.service.AuthService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@RequiredArgsConstructor +@Component +@Slf4j +public class AuthInterceptor implements HandlerInterceptor { + + private final AuthService authService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String token = request.getHeader("X-Subject-Token"); + if (token == null || token.isEmpty()) { + throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); + } + // 어드민과 일반 유저 구분 필요 + try { + if (authService.validateTokenAndGetUserId(token) != null) { + return true; + } + } catch (Exception e) { + log.error("Token validation failed for URI {}: {}", request.getRequestURI(), e.getMessage(), e); + throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); + } + throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); + } +} diff --git a/src/main/java/com/aolda/itda/config/WebConfig.java b/src/main/java/com/aolda/itda/config/WebConfig.java index 9eb992b64dc063a267fd03a19d9d071e56ce43b1..fd0080766c4816b4257be9c7c19dd0741d1da648 100644 --- a/src/main/java/com/aolda/itda/config/WebConfig.java +++ b/src/main/java/com/aolda/itda/config/WebConfig.java @@ -1,11 +1,17 @@ package com.aolda.itda.config; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration +@RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { + + private final AuthInterceptor authInterceptor; + @Override public void addCorsMappings(CorsRegistry registry) { // 스프링단에서 cors 설정 registry.addMapping("/**") @@ -16,4 +22,13 @@ public class WebConfig implements WebMvcConfigurer { .exposedHeaders("Authorization", "X-Refresh-Token", "Access-Control-Allow-Origin") ; } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + String[] excludeAuth = {"/error", "/api/auth/*" }; + + registry.addInterceptor(authInterceptor) + .addPathPatterns("/**") + .excludePathPatterns(excludeAuth); + } } diff --git a/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java b/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java new file mode 100644 index 0000000000000000000000000000000000000000..19786bd5a3c7d08eaa8d691ceea2d95e0ee62d15 --- /dev/null +++ b/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java @@ -0,0 +1,46 @@ +package com.aolda.itda.controller.forwarding; + +import com.aolda.itda.dto.forwarding.ForwardingDTO; +import com.aolda.itda.service.forwarding.ForwardingService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class ForwardingController { + + private final ForwardingService forwardingService; + + @PostMapping("/forwarding") + public ResponseEntity<Object> create(@RequestParam String projectId, + @RequestBody ForwardingDTO dto) { + forwardingService.createForwarding(projectId, dto); + return ResponseEntity.ok().build(); + } + + @GetMapping("/forwarding") + public ResponseEntity<Object> view(@RequestParam Long forwardingId) { + return ResponseEntity.ok(forwardingService.getForwarding(forwardingId)); + } + + @GetMapping("/forwardings") + public ResponseEntity<Object> lists(@RequestParam String projectId) { + return ResponseEntity.ok(forwardingService.getForwardings(projectId)); + } + + @PatchMapping("/forwarding") + public ResponseEntity<Object> edit(@RequestParam Long forwardingId, + @RequestBody ForwardingDTO dto) { + forwardingService.editForwarding(forwardingId, dto); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/forwarding") + public ResponseEntity<Object> delete(@RequestParam Long forwardingId) { + forwardingService.deleteForwarding(forwardingId); + return ResponseEntity.ok().build(); + } + +} diff --git a/src/main/java/com/aolda/itda/dto/PageResp.java b/src/main/java/com/aolda/itda/dto/PageResp.java new file mode 100644 index 0000000000000000000000000000000000000000..49a2a6cdbacdee106e68588dc7b22cb43bb70fa7 --- /dev/null +++ b/src/main/java/com/aolda/itda/dto/PageResp.java @@ -0,0 +1,27 @@ +package com.aolda.itda.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PageResp<T> { + private Integer totalPages; + private Integer totalElements; + private Integer size; + private List<T> contents; + private Boolean first; + private Boolean last; + + public static <T> PageRespBuilder<T> builderFor(Class<T> clazz) { + return (PageRespBuilder<T>) new PageRespBuilder<>(); + } +} diff --git a/src/main/java/com/aolda/itda/dto/forwarding/ForwardingDTO.java b/src/main/java/com/aolda/itda/dto/forwarding/ForwardingDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..3302d0a3b20500ec9731028aaa3390c5959a542a --- /dev/null +++ b/src/main/java/com/aolda/itda/dto/forwarding/ForwardingDTO.java @@ -0,0 +1,45 @@ +package com.aolda.itda.dto.forwarding; + +import com.fasterxml.jackson.annotation.JsonInclude; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ForwardingDTO { + + private Long id; + + @Pattern(regexp = "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$", + message = "잘못된 IP 형식 (server)") + private String serverIp; + + @NotBlank(message = "serverPort 값이 존재하지 않습니다") + @Pattern(regexp = "^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$", + message = "잘못된 포트 형식 (server)") + private String serverPort; + + @NotBlank(message = "instanceIp 값이 존재하지 않습니다") + @Pattern(regexp = "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$", + message = "잘못된 IP 형식 (instance)") + private String instanceIp; + + @NotBlank(message = "instancePort 값이 존재하지 않습니다") + @Pattern(regexp = "^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$", + message = "잘못된 포트 형식 (instance)") + private String instancePort; + + @NotBlank(message = "name 값이 존재하지 않습니다") + private String name; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java b/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..4460eb7b0c8c1401e10dcc56a34a09e81beca59a --- /dev/null +++ b/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java @@ -0,0 +1,26 @@ +package com.aolda.itda.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseTimeEntity { + + @CreatedDate + @Column(updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at") + private LocalDateTime updatedAt; + +} diff --git a/src/main/java/com/aolda/itda/entity/certificate/Certificate.java b/src/main/java/com/aolda/itda/entity/certificate/Certificate.java new file mode 100644 index 0000000000000000000000000000000000000000..5ab56a9239325d5745e7255d00e79c9538a21698 --- /dev/null +++ b/src/main/java/com/aolda/itda/entity/certificate/Certificate.java @@ -0,0 +1,46 @@ +package com.aolda.itda.entity.certificate; + +import com.aolda.itda.entity.BaseTimeEntity; +import com.aolda.itda.entity.user.User; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "certificate") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Certificate extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long certificateId; + + @OneToOne + @JoinColumn(nullable = false, name = "user_id") + private User user; + + private String projectId; + + private String domain; + + private String email; + + private LocalDateTime expiredAt; + + @Enumerated(EnumType.STRING) + private Challenge challenge; + + private Boolean isDeleted; + + private String description; + + +} diff --git a/src/main/java/com/aolda/itda/entity/certificate/Challenge.java b/src/main/java/com/aolda/itda/entity/certificate/Challenge.java new file mode 100644 index 0000000000000000000000000000000000000000..14713bffb86a0eb1a8d67162420bad39d6837b96 --- /dev/null +++ b/src/main/java/com/aolda/itda/entity/certificate/Challenge.java @@ -0,0 +1,5 @@ +package com.aolda.itda.entity.certificate; + +public enum Challenge { + HTTP, DNS_CLOUDFLARE +} diff --git a/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java b/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java new file mode 100644 index 0000000000000000000000000000000000000000..b0c8d34715c26ce8334e6037ea97c65329761c72 --- /dev/null +++ b/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java @@ -0,0 +1,71 @@ +package com.aolda.itda.entity.forwarding; + +import com.aolda.itda.dto.forwarding.ForwardingDTO; +import com.aolda.itda.entity.BaseTimeEntity; +import com.aolda.itda.entity.user.User; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "forwarding") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Forwarding extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long forwardingId; + + @OneToOne + @JoinColumn(name = "user_id") + private User user; + + private String projectId; + + private String serverIp; + + private String serverPort; + + private String instanceIp; + + private String instancePort; + + private Boolean isDeleted; + + private String name; + + public ForwardingDTO toForwardingDTO() { + return ForwardingDTO.builder() + .id(forwardingId) + .name(name) + .serverPort(serverPort) + .instanceIp(instanceIp) + .instancePort(instancePort) + .createdAt(getCreatedAt()) + .updatedAt(getUpdatedAt()) + .build(); + } + + public void edit(ForwardingDTO dto) { + this.name = dto.getName() != null ? dto.getName() : this.name; + this.serverPort = dto.getServerPort() != null && + dto.getServerPort().matches("^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$") + ? dto.getServerPort() : this.serverPort; + this.instanceIp = dto.getInstanceIp() != null && + dto.getInstanceIp().matches("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$") + ? dto.getInstanceIp() : this.instanceIp; + this.instancePort = dto.getInstancePort() != null && + dto.getInstancePort().matches("^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$") + ? dto.getInstancePort() : this.instancePort; + } + + public void delete() { + this.isDeleted = true; + } +} diff --git a/src/main/java/com/aolda/itda/entity/log/Action.java b/src/main/java/com/aolda/itda/entity/log/Action.java new file mode 100644 index 0000000000000000000000000000000000000000..a620f87a62fbb79faaf169690041b21c94c404f1 --- /dev/null +++ b/src/main/java/com/aolda/itda/entity/log/Action.java @@ -0,0 +1,5 @@ +package com.aolda.itda.entity.log; + +public enum Action { + CREATE, UPDATE, DELETE +} diff --git a/src/main/java/com/aolda/itda/entity/log/Log.java b/src/main/java/com/aolda/itda/entity/log/Log.java new file mode 100644 index 0000000000000000000000000000000000000000..fd8f460bba7940730772841c7451a8815412dd1b --- /dev/null +++ b/src/main/java/com/aolda/itda/entity/log/Log.java @@ -0,0 +1,40 @@ +package com.aolda.itda.entity.log; + +import com.aolda.itda.entity.BaseTimeEntity; +import com.aolda.itda.entity.user.User; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "log") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Log extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long logId; + + @OneToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + private String projectId; + + @Enumerated(EnumType.STRING) + private ObjectType objectType; + + private Long objectId; + + @Enumerated(EnumType.STRING) + private Action action; + + private String metadata; + +} diff --git a/src/main/java/com/aolda/itda/entity/log/ObjectType.java b/src/main/java/com/aolda/itda/entity/log/ObjectType.java new file mode 100644 index 0000000000000000000000000000000000000000..531031551afbfc5af9770dfc66cec886210715f4 --- /dev/null +++ b/src/main/java/com/aolda/itda/entity/log/ObjectType.java @@ -0,0 +1,5 @@ +package com.aolda.itda.entity.log; + +public enum ObjectType { + ROUTING, CERTIFICATE, FORWARDING +} diff --git a/src/main/java/com/aolda/itda/entity/routing/Routing.java b/src/main/java/com/aolda/itda/entity/routing/Routing.java new file mode 100644 index 0000000000000000000000000000000000000000..0c6b2aab53b5cc4a4611aaff02316febad651257 --- /dev/null +++ b/src/main/java/com/aolda/itda/entity/routing/Routing.java @@ -0,0 +1,43 @@ +package com.aolda.itda.entity.routing; + +import com.aolda.itda.entity.BaseTimeEntity; +import com.aolda.itda.entity.certificate.Certificate; +import com.aolda.itda.entity.user.User; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "routing") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Routing extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long routingId; + + @OneToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @OneToOne + @JoinColumn(name = "certificate_id", nullable = false) + private Certificate certificate; + + private String projectId; + + private String domain; + + private String instanceIp; + + private Boolean isDeleted; + + private String description; + +} diff --git a/src/main/java/com/aolda/itda/entity/user/User.java b/src/main/java/com/aolda/itda/entity/user/User.java new file mode 100644 index 0000000000000000000000000000000000000000..df006b22353175fd2cab784d624d096315547ca9 --- /dev/null +++ b/src/main/java/com/aolda/itda/entity/user/User.java @@ -0,0 +1,24 @@ +package com.aolda.itda.entity.user; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "user") +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long userId; + + private String keystoneUsername; + private String keystoneId; +} diff --git a/src/main/java/com/aolda/itda/exception/ErrorCode.java b/src/main/java/com/aolda/itda/exception/ErrorCode.java index 0916e71a1ef9a0824f3541ae257d490236c42cb3..2fc313bcb11256fdda7fc523a6eaf0ff6240c0a1 100644 --- a/src/main/java/com/aolda/itda/exception/ErrorCode.java +++ b/src/main/java/com/aolda/itda/exception/ErrorCode.java @@ -12,7 +12,15 @@ public enum ErrorCode { INVALID_USER_INFO(HttpStatus.BAD_REQUEST, "잘못된 회원 정보입니다"), // Token - INVALID_TOKEN(HttpStatus.BAD_REQUEST, "잘못된 토큰입니다"); + INVALID_TOKEN(HttpStatus.BAD_REQUEST, "잘못된 토큰입니다"), + + //Forwarding + FAIL_CREATE_CONF(HttpStatus.BAD_REQUEST, "Conf 파일을 생성하지 못했습니다"), + FAIL_UPDATE_CONF(HttpStatus.BAD_REQUEST, "Conf 파일을 수정하지 못했습니다"), + NOT_FOUND_FORWARDING(HttpStatus.BAD_REQUEST, "포트포워딩 파일이 존재하지 않습니다"), + INVALID_CONF_INPUT(HttpStatus.BAD_REQUEST, "잘못된 입력이 존재합니다"), + DUPLICATED_INSTANCE_INFO(HttpStatus.BAD_REQUEST, "중복된 인스턴스 IP와 포트입니다"), + DUPLICATED_SERVER_PORT(HttpStatus.BAD_REQUEST, "중복된 서버 포트입니다"); private final HttpStatus status; private final String message; diff --git a/src/main/java/com/aolda/itda/repository/forwarding/ForwardingRepository.java b/src/main/java/com/aolda/itda/repository/forwarding/ForwardingRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..c461762a59bc4cce3ca6fbde0f1de3277a4d7293 --- /dev/null +++ b/src/main/java/com/aolda/itda/repository/forwarding/ForwardingRepository.java @@ -0,0 +1,14 @@ +package com.aolda.itda.repository.forwarding; + +import com.aolda.itda.entity.forwarding.Forwarding; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface ForwardingRepository extends JpaRepository<Forwarding, Long> { + List<Forwarding> findByProjectIdAndIsDeleted(String projectId, Boolean isDeleted); + Optional<Forwarding> findByForwardingIdAndIsDeleted(Long forwardingId, Boolean isDeleted); + Boolean existsByInstanceIpAndInstancePortAndIsDeleted(String instanceIp, String instancePort, Boolean isDeleted); + Boolean existsByServerPortAndIsDeleted(String serverPort, Boolean isDeleted); +} diff --git a/src/main/java/com/aolda/itda/service/AuthService.java b/src/main/java/com/aolda/itda/service/AuthService.java index 1dea9a2772262ebad4d9e3aab1a2ebc0288c5de4..cc523fa856bbb7101e995e049bb7b3bdfb196127 100644 --- a/src/main/java/com/aolda/itda/service/AuthService.java +++ b/src/main/java/com/aolda/itda/service/AuthService.java @@ -15,7 +15,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import java.util.*; @@ -79,8 +78,13 @@ public class AuthService { "}"; HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers); - ResponseEntity<Map> res = restTemplate.postForEntity(url, requestEntity, Map.class); - + ResponseEntity<Map> res; + try { + res = restTemplate.postForEntity(url, requestEntity, Map.class); + } catch (Exception e) { + e.printStackTrace(); + throw new CustomException(ErrorCode.INVALID_USER_INFO); + } Map<String, Object> resToken = (Map<String, Object>) res.getBody().get("token"); Map<String, Object> resUser = (Map<String, Object>) resToken.get("user"); String userId = (String) resUser.get("id"); @@ -263,7 +267,7 @@ public class AuthService { return lists; } - private String validateTokenAndGetUserId(String token) throws JsonProcessingException { + public String validateTokenAndGetUserId(String token) throws JsonProcessingException { String url = keystone + "/auth/tokens"; HttpHeaders headers = new HttpHeaders(); headers.set("X-Auth-Token", token); @@ -273,7 +277,6 @@ public class AuthService { try { res = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class); } catch (HttpClientErrorException.NotFound e) { - System.out.println("validate"); throw new CustomException(ErrorCode.INVALID_TOKEN); } return objectMapper.readTree(res.getBody()).path("token").path("user").path("id").asText(); @@ -290,18 +293,14 @@ public class AuthService { res = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class); } catch (RuntimeException e) { e.printStackTrace(); - System.out.println("runtime"); return false; } JsonNode node = objectMapper.readTree(res.getBody()).path("role_assignments"); String system_all = node.path("scope").path("system").path("all").asText(); String role = node.path("role").path("name").asText(); - System.out.println("role: " + role); if (system_all.equals("true") && role.equals("admin")) { - System.out.println(system_all); return true; } - System.out.println("hi"); return false; } diff --git a/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java b/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java new file mode 100644 index 0000000000000000000000000000000000000000..e47001d1e855f70907114f4b7e5a4ea7ca4e0edf --- /dev/null +++ b/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java @@ -0,0 +1,183 @@ +package com.aolda.itda.service.forwarding; + +import com.aolda.itda.dto.PageResp; +import com.aolda.itda.dto.forwarding.ForwardingDTO; +import com.aolda.itda.entity.forwarding.Forwarding; +import com.aolda.itda.exception.CustomException; +import com.aolda.itda.exception.ErrorCode; +import com.aolda.itda.repository.forwarding.ForwardingRepository; +import com.aolda.itda.template.ForwardingTemplate; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Service +@Transactional +@RequiredArgsConstructor +public class ForwardingService { + + @Value("${spring.server.base-ip}") + private String serverBaseIp; + private final ForwardingTemplate forwardingTemplate; + private final ForwardingRepository forwardingRepository; + + /* 포트포워딩 정보 조회 */ + public ForwardingDTO getForwarding(Long forwardingId) { + Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(forwardingId, false) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + return forwarding.toForwardingDTO(); + } + + /* 포트포워딩 목록 조회 */ + public PageResp<ForwardingDTO> getForwardings(String projectId) { + + return PageResp.<ForwardingDTO>builder() + .contents(forwardingRepository.findByProjectIdAndIsDeleted(projectId, false) + .stream() + .map(Forwarding::toForwardingDTO) + .toList()).build(); + } + + /* 포트포워딩 생성 */ + public void createForwarding(String projectId, ForwardingDTO dto) { + + /* 입력 DTO 검증 */ + validateDTO(dto); + + /* 중복 검증 */ + if (forwardingRepository.existsByInstanceIpAndInstancePortAndIsDeleted(dto.getInstanceIp(), dto.getInstancePort(), false)) { + throw new CustomException(ErrorCode.DUPLICATED_INSTANCE_INFO); + } + + if (forwardingRepository.existsByServerPortAndIsDeleted(dto.getServerPort(), false)) { + throw new CustomException(ErrorCode.DUPLICATED_SERVER_PORT); + } + + /* 포트포워딩 엔티티 생성 */ + Forwarding forwarding = Forwarding.builder() + .isDeleted(false) + .projectId(projectId) + .name(dto.getName()) + .serverIp(dto.getServerIp() == null ? serverBaseIp : dto.getServerIp()) + .serverPort(dto.getServerPort()) + .instanceIp(dto.getInstanceIp()) + .instancePort(dto.getInstancePort()) + .build(); + + forwardingRepository.save(forwarding); + + /* nginx conf 파일 생성 및 예외 처리 */ + String content = forwardingTemplate.getPortForwardingWithTCP(dto.getServerPort(), dto.getInstanceIp(), dto.getInstancePort(), dto.getName()); + String confPath = "/data/nginx/stream/" + forwarding.getForwardingId() + ".conf"; + + File file = new File(confPath); + try { + Path path = Paths.get(confPath); + Files.createDirectories(path.getParent()); + if (!file.createNewFile()) { + throw new CustomException(ErrorCode.FAIL_CREATE_CONF, "중복된 포트포워딩 Conf 파일이 존재합니다"); + } + } catch (IOException e) { + e.printStackTrace(); + throw new CustomException(ErrorCode.FAIL_CREATE_CONF); + } + + /* conf 파일 작성 및 예외 처리 */ + try { + BufferedWriter bw = new BufferedWriter(new FileWriter(file, true)); // 예외처리 필요 + bw.write(content); + bw.flush(); + bw.close(); + } catch (Exception e) { + e.printStackTrace(); + if (file.exists()) { + file.delete(); + } + throw new CustomException(ErrorCode.FAIL_CREATE_CONF, "포트포워딩 Conf 파일을 작성하지 못했습니다"); + } + + + } + + /* 포트포워딩 정보 수정 */ + public void editForwarding(Long forwardingId, ForwardingDTO dto) { + Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(forwardingId, false) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + + forwarding.edit(dto); + + /* 중복 검증 */ + if (!(dto.getInstanceIp() == null && dto.getInstancePort() == null) && + forwardingRepository.existsByInstanceIpAndInstancePortAndIsDeleted(forwarding.getInstanceIp() + , forwarding.getInstancePort() + , false)) { + throw new CustomException(ErrorCode.DUPLICATED_INSTANCE_INFO); + } + + if (dto.getServerPort() != null && forwardingRepository.existsByServerPortAndIsDeleted(dto.getServerPort(), false)) { + throw new CustomException(ErrorCode.DUPLICATED_SERVER_PORT); + } + + /* 파일 수정 */ + String content = forwardingTemplate.getPortForwardingWithTCP(forwarding.getServerPort(), + forwarding.getInstanceIp(), + forwarding.getInstancePort(), + forwarding.getName()); + String confPath = "/data/nginx/stream/" + forwarding.getForwardingId() + ".conf"; + File file = new File(confPath); + if (!file.exists()) { + throw new CustomException(ErrorCode.NOT_FOUND_FORWARDING, "Conf 파일이 존재하지 않아 수정할 수 없습니다"); + } + + try { + BufferedWriter bw = new BufferedWriter(new FileWriter(file, true)); // 예외처리 필요 + bw.write(content); + bw.flush(); + bw.close(); + } catch (Exception e) { + e.printStackTrace(); + throw new CustomException(ErrorCode.FAIL_UPDATE_CONF, "포트포워딩 Conf 파일을 수정하지 못했습니다"); + } + + /* DB 정보 수정 */ + forwardingRepository.save(forwarding); + } + + /* 포트포워딩 삭제 (소프트) */ + public void deleteForwarding(Long forwardingId) { + Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(forwardingId, false) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + + /* 파일 삭제 */ + String confPath = "/data/nginx/stream/" + forwarding.getForwardingId() + ".conf"; + File file = new File(confPath); + if (!file.delete()) { + throw new CustomException(ErrorCode.NOT_FOUND_FORWARDING, "Conf 파일이 존재하지 않아 삭제할 수 없습니다"); + } + + /* DB */ + forwarding.delete(); + forwardingRepository.save(forwarding); + + } + + /* 입력 DTO 검증 */ + private void validateDTO(ForwardingDTO dto) { + + for (ConstraintViolation<ForwardingDTO> violation : Validation.buildDefaultValidatorFactory().getValidator().validate(dto)) { + throw new CustomException(ErrorCode.INVALID_CONF_INPUT, violation.getMessage()); + } + + } +} diff --git a/src/main/java/com/aolda/itda/template/ForwardingTemplate.java b/src/main/java/com/aolda/itda/template/ForwardingTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..6d8bf2cedcba6f6122cb7b93b80b95078745554d --- /dev/null +++ b/src/main/java/com/aolda/itda/template/ForwardingTemplate.java @@ -0,0 +1,16 @@ +package com.aolda.itda.template; + +import org.springframework.stereotype.Component; + +@Component +public class ForwardingTemplate { + + public String getPortForwardingWithTCP(String serverPort, String instanceIp, String instancePort, String name) { + return "# " + name + "\n" + + "server { \n" + + " listen " + serverPort + "; \n" + + " listen [::]:" + serverPort + "; \n" + + " proxy_pass " + instanceIp + ":" + instancePort + ";\n" + + "} \n"; + } +} diff --git a/src/main/java/com/aolda/itda/template/OptionTemplate.java b/src/main/java/com/aolda/itda/template/OptionTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..3e1e92c77efaf1eebec1af4cc1b283255c501a0e --- /dev/null +++ b/src/main/java/com/aolda/itda/template/OptionTemplate.java @@ -0,0 +1,26 @@ +package com.aolda.itda.template; + +import org.springframework.stereotype.Component; + +@Component +public class OptionTemplate { + + public String getSSL(Long certificateId) { + return "\nconf.d/include/letsencrypt-acme-challenge.conf;\n" + + "include conf.d/include/ssl-ciphers.conf;\n" + + "ssl_certificate /etc/letsencrypt/live/npm-" + certificateId + "/fullchain.pem;\n" + + "ssl_certificate_key /etc/letsencrypt/live/npm-" + certificateId + "/privkey.pem;\n"; + } + + public String getAssetCaching() { + return "include conf.d/include/assets.conf;\n"; + } + + public String getBlockExploits() { + return "include conf.d/include/block-exploits.conf;\n"; + } + + public String getForceSSL() { + return "include conf.d/include/force-ssl.conf;\n"; + } +}