Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .github/workflows/deploy.yml
Empty file.
10 changes: 7 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ services:
- mysql
- redis
environment:
SPRING_PROFILES_ACTIVE: docker
SPRING_PROFILES_ACTIVE: prod
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API service now uses "prod" profile while the Batch service still uses "docker" profile (line 70 in docker-compose.yml). For consistency and clarity in deployment configuration, consider aligning both services to use the same profile naming convention (either both use "prod" or both use "docker"). This inconsistency could lead to confusion about which environment is being used.

Copilot uses AI. Check for mistakes.
env_file:
- .env
networks:
Expand Down Expand Up @@ -85,14 +85,18 @@ services:
networks:
- app-network

kafka:
kafka-1:
image: confluentinc/cp-kafka:7.4.0
ports:
- "9092:9092" #도커 내부 포트 9092를 내 컴퓨터 포트 9092에 매핑
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 # 도커 외부에서 도는 API 서버가 카프카를 찾아갈 수 있음
# ✅ 리스너 이름을 서비스 이름인 kafka-1로 설정
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:29092,CONTROLLER://0.0.0.0:9092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka-1:29092,CONTROLLER://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
depends_on:
- zookeeper
Expand Down
3 changes: 3 additions & 0 deletions services/api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ dependencies {
// 테스트를 위한 설정 (선택 사항)
testImplementation 'org.springframework.kafka:spring-kafka-test'

implementation 'org.springframework.boot:spring-boot-starter-actuator'


}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,16 @@ public PasswordEncoder passwordEncoder() {
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();

// 프론트엔드 포트 허용
configuration.setAllowedOrigins(List.of("http://localhost:5173"));
// [핵심 수정] 환경 변수에서 프론트엔드 주소를 가져오고, 없으면 로컬 주소를 기본값으로 사용
String frontendUrl = System.getenv("FRONTEND_URL");
if (frontendUrl == null) frontendUrl = "http://localhost:5173";

// 로컬과 실제 배포 주소를 모두 허용 목록에 넣습니다.
configuration.setAllowedOrigins(List.of(frontendUrl, "http://localhost:5173"));
Comment on lines +48 to +52
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When FRONTEND_URL environment variable is not set, "http://localhost:5173" will be added twice to the allowed origins list (once from the fallback on line 49, and once explicitly on line 52). While this won't break functionality, it's redundant. Consider using a conditional list construction or removing the explicit localhost entry if frontendUrl already contains it.

Copilot uses AI. Check for mistakes.

configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
// 쿠키 전송을 위해 필수 설정
configuration.setAllowCredentials(true);
configuration.setAllowCredentials(true); // 쿠키/인증정보 전송 허용
Comment on lines +47 to +56
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the CORS configuration now supports environment-based frontend URLs, the actuator endpoints added in application.properties (line 11 exposes all endpoints) are not explicitly permitted in the SecurityConfig. Since the SecurityConfig requires authentication for all requests except SSE and specific auth endpoints, the actuator endpoints will be blocked. This will cause ALB health checks to fail. Consider adding a requestMatcher to permit access to actuator health endpoints.

Copilot uses AI. Check for mistakes.

Comment on lines +56 to 57
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSRF protection is disabled (line 84 in the unchanged code). While the comment mentions it's for local development, the active profile is now set to "prod" and this configuration should be reviewed for production deployment. The commented code (lines 92-95) suggests CSRF should be enabled for production. Consider enabling CSRF protection with appropriate token handling for the production environment.

Suggested change
configuration.setAllowCredentials(true); // 쿠키/인증정보 전송 허용
// 운영 환경(prod)에서는 CSRF 위험을 줄이기 위해 CORS 자격 증명(쿠키 등) 전송을 비활성화합니다.
// SPRING_PROFILES_ACTIVE=prod 인 경우만 false, 그 외(로컬/개발 등)는 기존 동작 유지(true).
String activeProfile = System.getenv("SPRING_PROFILES_ACTIVE");
boolean allowCredentials = activeProfile == null || !"prod".equalsIgnoreCase(activeProfile.trim());
configuration.setAllowCredentials(allowCredentials);

Copilot uses AI. Check for mistakes.
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
Expand Down Expand Up @@ -110,12 +114,3 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.build();
}
}

/* 테스트 코드
.csrf(csrf -> csrf.disable()) // 테스트 위해 임시허용
// 모든 경로에 대해 접근 허용
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
);
http.addFilterBefore(customLoginFilter(), UsernamePasswordAuthenticationFilter.class);
*/
26 changes: 18 additions & 8 deletions services/api/src/main/resources/application-prod.properties
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
# ----- API는 local, MySQL는 Docker -----
spring.datasource.url=jdbc:mysql://mysql:3306/appdb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
# 1. Database (RDS \uC5F0\uACB0\uC6A9)
# \uD658\uACBD \uBCC0\uC218\uAC00 \uC788\uC73C\uBA74 \uADF8 \uAC12\uC744 \uC4F0\uACE0, \uC5C6\uC73C\uBA74 \uAE30\uBCF8\uAC12(mysql:3306)\uC744 \uC0AC\uC6A9\uD558\uB3C4\uB85D \uC124\uC815\uD568
spring.datasource.url=jdbc:mysql://${RDS_HOSTNAME:mysql}:${RDS_PORT:3306}/${RDS_DB_NAME:appdb}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The production JDBC URL uses useSSL=false, which turns off TLS for connections to your RDS/MySQL instance despite support for encrypted transport. This allows anyone with access to the network path between the API service and the database (including compromised pods/instances or network eavesdroppers) to observe credentials and sensitive data in cleartext. Update the datasource configuration to enforce SSL/TLS for database connections (e.g., useSSL/requireSSL enabled with proper CA configuration) so that all traffic to RDS is encrypted.

Suggested change
spring.datasource.url=jdbc:mysql://${RDS_HOSTNAME:mysql}:${RDS_PORT:3306}/${RDS_DB_NAME:appdb}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
spring.datasource.url=jdbc:mysql://${RDS_HOSTNAME:mysql}:${RDS_PORT:3306}/${RDS_DB_NAME:appdb}?useSSL=true&requireSSL=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul

Copilot uses AI. Check for mistakes.
spring.datasource.username=${RDS_USERNAME:app}
spring.datasource.password=${RDS_PASSWORD:app}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=app
spring.datasource.password=app

# JPA
# 2. JPA (\uC6B4\uC601 \uD658\uACBD \uC8FC\uC758!)
# \uBC30\uD3EC \uC2DC\uC5D0\uB294 'update'\uBCF4\uB2E4 'none'\uC774\uB098 'validate'\uAC00 \uC548\uC804\uD558\uC9C0\uB9CC, \uCD08\uAE30 \uAD6C\uCD95 \uC2DC\uC5D0\uB294 update\uB97C \uC720\uC9C0\uD574\uB3C4 \uB428
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true

# schema.sql/data.sql ???? ????? ??? ? ???(??? always?)
spring.sql.init.mode=never
# 3. Redis (ElastiCache \uC5F0\uACB0\uC6A9)
spring.data.redis.host=${REDIS_HOST:my-redis}
spring.data.redis.port=${REDIS_PORT:6379}

# 4. Kafka (MSK \uB610\uB294 Cloud Kafka \uC5F0\uACB0\uC6A9)
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default Kafka bootstrap server is set to "kafka-1:29092" which matches the docker-compose.yml service name and internal port. However, this assumes the API service will always run in the same Docker network. For AWS deployment (as indicated in the PR description), this should be configured to use MSK endpoints via environment variables, which is already supported. Consider documenting this or adjusting the default value to indicate it's for local Docker environments only.

Suggested change
# 4. Kafka (MSK \uB610\uB294 Cloud Kafka \uC5F0\uACB0\uC6A9)
# 4. Kafka (MSK \uB610\uB294 Cloud Kafka \uC5F0\uACB0\uC6A9)
# \uAE30\uBCF8\uAC12 kafka-1:29092\uB294 docker-compose.yml \uC0AC\uC6A9 \uC2DC \uB3D9\uC77C \uB124\uD2B8\uC6CC\uD06C\uC5D0\uC11C \uB3D9\uC791\uD558\uB294 \uB85C\uCEEC Docker \uD658\uACBD\uC6A9 \uC124\uC815\uC785\uB2C8\uB2E4.
# AWS (MSK) \uBC0F \uC2E4\uC81C \uC6B4\uC601 \uD658\uACBD\uC5D0\uC11C\uB294 \uD544\uC218\uB85C KAFKA_BROKERS \uD658\uACBD \uBCC0\uC218\uB97C MSK \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uB85C \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.

Copilot uses AI. Check for mistakes.
spring.kafka.bootstrap-servers=${KAFKA_BROKERS:kafka-1:29092}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Kafka producer and consumer serializer/deserializer configurations are missing from the prod profile. While application-local.properties includes these configurations (lines 27-35 in application-local.properties), the prod profile only specifies the bootstrap servers. This could cause runtime errors when Kafka is used in production. Add the serializer/deserializer configurations similar to those in application-local.properties.

Suggested change
spring.kafka.bootstrap-servers=${KAFKA_BROKERS:kafka-1:29092}
spring.kafka.bootstrap-servers=${KAFKA_BROKERS:kafka-1:29092}
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer

Copilot uses AI. Check for mistakes.

# 5. Actuator (\uBCF4\uC548\uC744 \uC704\uD574 health\uB9CC \uB178\uCD9C)
management.endpoints.web.exposure.include=health
management.endpoint.health.show-details=always
7 changes: 5 additions & 2 deletions services/api/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
spring.application.name=api
spring.profiles.active=local
spring.profiles.active=prod

# JWT \uC124\uC815
jwt.secret=${JWT_SECRET}

# TMDB API \uD1A0\uD070 (\uACF5\uD1B5)
custom.tmdb.access-token=${TMDB_ACCESS_TOKEN}
custom.tmdb.access-token=${TMDB_ACCESS_TOKEN}

# Actuator endpoint \uBAA8\uB450 \uB178\uCD9C
management.endpoints.web.exposure.include=*
Comment on lines +10 to +11
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actuator endpoints are exposed to all (*) in the base application.properties, which conflicts with the more restrictive configuration in application-prod.properties (line 22) that only exposes health. Since the active profile is set to "prod" (line 2), this creates a security risk as the application-prod.properties configuration will be more restrictive, but this configuration applies to all profiles and could be used if someone changes the active profile. Consider removing this line or setting it to only expose health endpoints for consistency and security.

Suggested change
# Actuator endpoint \uBAA8\uB450 \uB178\uCD9C
management.endpoints.web.exposure.include=*
# Actuator endpoint health \uB9CC \uB178\uCD9C
management.endpoints.web.exposure.include=health

Copilot uses AI. Check for mistakes.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
server.port=8081

spring.datasource.url=jdbc:mysql://mysql:3306/appdb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
spring.datasource.username=app
spring.datasource.password=app
spring.datasource.url=jdbc:mysql://${RDS_HOSTNAME:mysql}:${RDS_PORT:3306}/${RDS_DB_NAME:appdb}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JDBC URL explicitly sets useSSL=false, which disables TLS for connections to your database even though encrypted connections are available. An attacker with network visibility inside the VPC or container network could sniff database credentials and data in transit. Configure the datasource to require SSL/TLS for MySQL/RDS (e.g., enabling useSSL/requireSSL and using the RDS CA or equivalent secure settings) so that all database traffic is encrypted.

Suggested change
spring.datasource.url=jdbc:mysql://${RDS_HOSTNAME:mysql}:${RDS_PORT:3306}/${RDS_DB_NAME:appdb}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul
spring.datasource.url=jdbc:mysql://${RDS_HOSTNAME:mysql}:${RDS_PORT:3306}/${RDS_DB_NAME:appdb}?useSSL=true&requireSSL=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul

Copilot uses AI. Check for mistakes.
spring.datasource.username=${RDS_USERNAME:app}
spring.datasource.password=${RDS_PASSWORD:app}

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
Expand Down
81 changes: 45 additions & 36 deletions services/gateway/nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -1,39 +1,48 @@
# Nginx 전체 설정 파일
worker_processes auto;

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

sendfile on;
keepalive_timeout 65;

# 1. 기존 include는 주석 처리하거나 그대로 둡니다.
# include /etc/nginx/conf.d/*.conf;

# 2. 서버 설정
server {
listen 80;

# 일반 API 요청 (로컬 API 서버 연결)
location /api/ {
proxy_pass http://host.docker.internal:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

# SSE 전용 설정 (실시간 알림용)
location /api/sse {
proxy_pass http://host.docker.internal:8080;
proxy_set_header Connection "";
proxy_http_version 1.1;
proxy_buffering off; # 새로고침 안해도 알람 전송해주는 설정
proxy_cache off;
proxy_read_timeout 3600s;
}
}
}
events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

sendfile on;
keepalive_timeout 65;

server {
listen 80;

# [필수] AWS 내부 DNS 서버 주소 (Service Discovery를 찾기 위해 꼭 필요함)
resolver 10.0.0.2 valid=30s;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resolver configuration uses a hardcoded IP address (10.0.0.2) which is AWS VPC's default DNS server. This is correct for AWS deployments, but it will break in other cloud providers or non-AWS environments. Consider making this configurable via environment variables or documenting this AWS-specific requirement clearly in the deployment documentation.

Copilot uses AI. Check for mistakes.

# [필수] 변수 정의: API 서버의 내부 도메인 주소
set $api_url http://api.mopl.local:8080;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nginx configuration now uses AWS Service Discovery domain (api.mopl.local:8080). This is specific to AWS Cloud Map service discovery. If the API service is not registered with this exact domain in AWS Service Discovery, the gateway will fail to route traffic. Ensure that the ECS service is configured with the correct Cloud Map namespace and service name, or consider making this domain configurable via environment variables for flexibility.

Copilot uses AI. Check for mistakes.

# 1. 일반 API 요청
location /api/ {
proxy_pass $api_url;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

# 2. SSE 전용 설정 (실시간 알림용)
location /api/sse {
proxy_pass $api_url;
proxy_set_header Connection "";
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
}

# [추가] ALB 건강 체크용 (배포 시 필수)
location /health {
access_log off;
return 200 'OK';
}
}
}
Loading