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
20 changes: 20 additions & 0 deletions monew/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ services:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5
deploy:
resources:
limits:
cpus: "4.0"
memory: 2g
reservations:
cpus: "2.0"
memory: 1g

redis:
image: redis:7
Expand All @@ -26,6 +34,11 @@ services:
- "6379:6379"
networks:
- monew-net
deploy:
resources:
limits:
cpus: "1.0"
memory: 512m

mongo:
image: mongo:6
Expand All @@ -36,6 +49,11 @@ services:
- "27017:27017"
networks:
- monew-net
deploy:
resources:
limits:
cpus: "2.0"
memory: 1g

app:
build:
Expand All @@ -58,6 +76,8 @@ services:
SPRING_DATA_REDIS_HOST: redis
BATCH_TRIGGER_ENABLED: true
SPRING_BATCH_JDBC_INITIALIZE_SCHEMA: always
NAVER_CLIENT_ID: ${NAVER_CLIENT_ID}
NAVER_CLIENT_SECRET: ${NAVER_CLIENT_SECRET}
Comment on lines +79 to +80
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

환경 변수 설정 확인 필요

네이버 API 자격증명을 환경 변수로 주입하는 방식은 적절합니다. 하지만 docker-compose up 실행 전에 해당 환경 변수가 설정되어 있는지 확인이 필요합니다.

다음을 확인해주세요:

  1. .env 파일 또는 셸 환경에 NAVER_CLIENT_IDNAVER_CLIENT_SECRET이 설정되어 있는지 확인
  2. 프로덕션 환경에서는 Docker Secrets 사용을 고려

🏁 Script executed:

#!/bin/bash
# Description: Check if .env file exists and contains NAVER credentials

if [ -f "monew/.env" ]; then
  echo "Found .env file:"
  grep -E "NAVER_CLIENT_(ID|SECRET)" monew/.env || echo "NAVER credentials not found in .env"
else
  echo ".env file not found. Make sure to set environment variables before running docker-compose."
fi

Length of output: 90



---

🏁 Script executed:

```shell
#!/bin/bash
# Check for environment setup documentation and examples

echo "=== Checking for .env.example or similar ===" 
fd -e env -e example monew/ 2>/dev/null | head -20

echo ""
echo "=== Checking for README or setup docs ==="
fd -i -e md monew/ 2>/dev/null | xargs grep -l "NAVER\|환경 변수\|environment" 2>/dev/null || echo "No matches found"

echo ""
echo "=== Checking docker-compose.yml for comments about credentials ==="
head -100 monew/docker-compose.yml

Length of output: 2406



---

🏁 Script executed:

```shell
#!/bin/bash
# Check root directory for setup documentation

echo "=== Root directory README and docs ==="
ls -la | grep -iE "readme|doc|setup|env|contributing" || echo "No matches in root"

echo ""
echo "=== Checking if root README mentions environment variables ==="
if [ -f "README.md" ]; then
  head -50 README.md
else
  echo "No README.md in root"
fi

echo ""
echo "=== Checking for any .env template files ==="
find . -maxdepth 2 -type f \( -name ".env*" -o -name "*.env" \) 2>/dev/null || echo "No .env template files"

Length of output: 455


.env.example 템플릿 파일 추가 필요

네이버 API 자격증명(NAVER_CLIENT_ID, NAVER_CLIENT_SECRET)이 환경 변수로 주입되도록 설정되어 있으나, 프로젝트에 .env.example 템플릿이 없어 개발자가 어떤 환경 변수를 설정해야 하는지 알 수 없습니다. monew/ 디렉토리에 .env.example 파일을 생성하여 필수 환경 변수를 명시해주세요.

예시:

NAVER_CLIENT_ID=your_naver_client_id
NAVER_CLIENT_SECRET=your_naver_client_secret

개발자는 docker-compose up 실행 전에 이 파일을 복사하여 .env 파일을 만들고 실제 값을 입력해야 합니다.

🤖 Prompt for AI Agents
In monew/docker-compose.yml around lines 79-80 the compose file expects
NAVER_CLIENT_ID and NAVER_CLIENT_SECRET from an environment file but the
repository lacks a .env.example template; create monew/.env.example listing the
required variables (NAVER_CLIENT_ID and NAVER_CLIENT_SECRET) with placeholder
values and a short comment instructing developers to copy it to .env and
populate real credentials before running docker-compose up.

healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"]
interval: 10s
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.spring.monew.auth.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

사용되지 않는 로깅 어노테이션 제거 필요

@Slf4j 어노테이션이 추가되었지만 코드 내에서 log를 사용하는 곳이 없습니다.

다음과 같이 제거하는 것을 권장합니다:

 package com.spring.monew.auth.config;
 
 import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
-@Slf4j
 @RequiredArgsConstructor
 @Configuration
 @EnableWebSecurity

Also applies to: 17-17

🤖 Prompt for AI Agents
In monew/src/main/java/com/spring/monew/auth/config/SecurityConfig.java around
lines 4 and 17, the Lombok @Slf4j import and annotation are unused; remove the
"import lombok.extern.slf4j.Slf4j;" line and delete the "@Slf4j" annotation on
the class declaration, and then run a build / static-analysis to ensure no
remaining references to log exist and no new unused-import warnings are
introduced.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -14,6 +15,7 @@
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.spring.monew.common.filter.RequestIdFilter;

@Slf4j
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
Expand All @@ -22,20 +24,26 @@ public class SecurityConfig {
private final HeaderAuthFilter headerAuthFilter;
private final RequestIdFilter requestIdFilter;

@Value("${monitoring.prometheus.allow-ip}")
private String prometheusAllowIp; // yml 속성 주입 (기본값은 localhost)
@Value("${monitoring.prometheus.allow-ip:127.0.0.1}")
private String prometheusAllowIp;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // CSRF 보안 비활성화 (개발용)

.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/actuator/prometheus")
.access((authentication, context) -> {
String remoteAddr = context.getRequest().getRemoteAddr();
boolean equals = remoteAddr.equals(prometheusAllowIp);// Prometheus IP
return new AuthorizationDecision(equals);
boolean allowed =
remoteAddr.equals("127.0.0.1") ||
remoteAddr.equals("0:0:0:0:0:0:0:1") || // IPv6 localhost
remoteAddr.equals("localhost") ||
remoteAddr.startsWith("172.") || // Docker 내부 네트워크
remoteAddr.startsWith("192.168.") ||
remoteAddr.equals(prometheusAllowIp);

return new AuthorizationDecision(allowed);
Comment on lines +38 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

프로덕션 환경에서 과도하게 허용적인 Prometheus 접근 제어

Prometheus 메트릭 엔드포인트 접근을 IP 기반으로만 제어하고 있으며, Docker 네트워크 전체(172.*)와 사설 네트워크 전체(192.168.*)를 허용하고 있습니다. 개발 및 테스트 환경에서는 적절하지만, 프로덕션 환경에서는 보안 위험이 있습니다.

다음 개선사항을 고려해주세요:

  1. 환경별 설정 분리: 프로덕션과 개발 환경에서 다른 접근 제어 정책 적용
  2. 더 제한적인 IP 범위: 프로덕션에서는 특정 모니터링 서버 IP만 허용
  3. 추가 인증 계층: IP 기반 필터링 외에 Basic Auth 또는 토큰 기반 인증 추가
// 환경별 설정 예시
@Value("${spring.profiles.active:local}")
private String activeProfile;

// 접근 제어 로직에서
boolean allowed;
if ("prod".equals(activeProfile)) {
  // 프로덕션: 특정 IP만 허용
  allowed = remoteAddr.equals(prometheusAllowIp);
} else {
  // 개발/테스트: 넓은 범위 허용
  allowed = remoteAddr.equals("127.0.0.1") ||
            remoteAddr.equals("0:0:0:0:0:0:0:1") ||
            remoteAddr.startsWith("172.") ||
            remoteAddr.startsWith("192.168.") ||
            remoteAddr.equals(prometheusAllowIp);
}
🤖 Prompt for AI Agents
In monew/src/main/java/com/spring/monew/auth/config/SecurityConfig.java around
lines 36 to 44, the current logic allows broad IP ranges (172.*, 192.168.* and
localhost variants) which is too permissive for production; change to
environment-aware authorization by injecting the active profile (e.g.
@Value("${spring.profiles.active:local}") private String activeProfile) and
branch the decision: if profile equals "prod" only allow the specific monitoring
IP(s) (prometheusAllowIp or a configured list) and reject all others; if
non-prod keep the current relaxed checks; additionally, add an extra auth layer
for the metrics endpoint in prod (Basic Auth or token-based check) and ensure
prometheusAllowIp is validated/parsed as a single IP or CIDR list rather than
matching prefixes.

})
.requestMatchers("/actuator/health", "/actuator/info",
"/actuator/loggers").permitAll() //Actuator 허용 (원래는 이렇게 하면 안됨)
Expand All @@ -52,7 +60,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.build();
}


@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
Expand Down
11 changes: 10 additions & 1 deletion monew/src/main/resources/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ CREATE TABLE articles
id UUID PRIMARY KEY,
interest_id UUID NOT NULL,
source VARCHAR(20) NOT NULL CHECK (source IN ('NAVER', 'HANKYUNG', 'CHOSUN', 'YEONHAP')),
source_url VARCHAR(255) NOT NULL UNIQUE,
source_url VARCHAR(500) NOT NULL UNIQUE,
title VARCHAR(255) NOT NULL,
publish_date TIMESTAMP WITH TIME ZONE NOT NULL,
summary TEXT NOT NULL,
Expand Down Expand Up @@ -286,3 +286,12 @@ CREATE INDEX IF NOT EXISTS idx_notif_user_confirm_created_desc
-- 정리 배치(삭제): WHERE confirmed=true AND updated_at < :threshold
CREATE INDEX IF NOT EXISTS idx_notif_confirmed_updated
ON notifications (confirmed, updated_at);

-- comments Indexing
-- (1) createdAt 기반 정렬
CREATE INDEX IF NOT EXISTS idx_comment_article_created
ON comments (article_id, is_deleted, created_at DESC, id);

-- (2) likeCount 정렬 대비
CREATE INDEX IF NOT EXISTS idx_comment_article_like
ON comments (article_id, is_deleted, like_count DESC, created_at DESC, id);