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
57 changes: 57 additions & 0 deletions .github/workflows/ci-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: CI-PROD

on:
pull_request:
branches: [ main, dev ]
push:
branches: [ main, dev ]
Comment thread
Sehi55 marked this conversation as resolved.

permissions:
contents: read # 내 코드를 읽을 수 있게(코드이동) 접근 권한을 준다.
id-token: write # OIDC 토큰을 발급할 수 있게 한다.

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout # 내 코드 이동 깃허브 컴퓨터로
uses: actions/checkout@v4

- name: Set up JDK 17 # jdk 환경 설치
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4

- name: Grant execute permission for gradlew
run: chmod +x ./gradlew

- name: Run tests # 테스트 실행
run: ./gradlew test --no-daemon

- name: Generate image tag
run: echo "IMAGE_TAG=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_ENV

- name: Configure AWS credentials (OIDC) # OIDC 기반으로 역할 자격증명
if: github.event_name == 'push'
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ vars.AWS_REGION }}

- name: Login to Amazon ECR # 위에서 자격증명하고 ecr 로그인
if: github.event_name == 'push'
uses: aws-actions/amazon-ecr-login@v2

- name: Build Docker image # 이미지 빌드해서
run: |
docker build --build-arg SERVER_PORT=${{ vars.SERVER_PORT }} -t ${{ vars.ECR_REGISTRY }}/${{ vars.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }} .

- name: Push Docker image to ECR # 이미지 ecr로 업로드
if: github.event_name == 'push'
run: |
docker push ${{ vars.ECR_REGISTRY }}/${{ vars.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

Expand Down Expand Up @@ -62,6 +61,7 @@ Temporary Items
*.ctxt
.mtj.tmp/
*.jar
!gradle/wrapper/gradle-wrapper.jar
*.war
*.nar
*.ear
Expand Down Expand Up @@ -105,4 +105,4 @@ $RECYCLE.BIN/

### Docker ###
*.tar
docker-compose.override.yml
docker-compose.override.yml
21 changes: 13 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
FROM eclipse-temurin:17-jre
FROM eclipse-temurin:17-jdk AS builder
WORKDIR /app

# 1. non-root 전용 유저 및 그룹 생성 (appuser)
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY gradlew .
COPY gradle gradle
COPY build.gradle settings.gradle ./
COPY src src

RUN chmod +x ./gradlew
RUN ./gradlew bootJar -x test -x asciidoctor --no-daemon

# 2. 호스트(로컬 PC)에서 이미 빌드된 jar 파일을 복사 (파일 소유권 자동 지정)
COPY --chown=appuser:appuser build/libs/*-SNAPSHOT.jar app.jar
FROM eclipse-temurin:17-jre
WORKDIR /app

# 3. 컨테이너 실행 권한을 appuser로 전환
USER appuser
COPY --from=builder /app/build/libs/*.jar app.jar

EXPOSE 19900
ARG SERVER_PORT=19900
EXPOSE ${SERVER_PORT}

ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Comment thread
ji-circle marked this conversation as resolved.
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ dependencies {
testImplementation 'org.testcontainers:junit-jupiter'

testImplementation 'org.springframework.security:spring-security-test'

runtimeOnly 'io.micrometer:micrometer-registry-prometheus'

testImplementation 'org.testcontainers:testcontainers'
}

dependencyManagement {
Expand All @@ -90,3 +94,8 @@ bootJar {
dependsOn asciidoctor
from("${asciidoctor.outputDir}") { into 'static/docs' }
}

// plain jar 생성 방지
jar {
enabled = false
}
Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
59 changes: 59 additions & 0 deletions src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
server:
port: ${SERVER_PORT:19900}

spring:
application:
name: inventory-service

datasource:
url: jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
username: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
driver-class-name: org.postgresql.Driver

data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT:6379}

kafka:
bootstrap-servers: ${KAFKA_BROKERS}
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
consumer:
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
group-id: ${spring.application.name}-group

jpa:
hibernate:
ddl-auto: update
Comment on lines +29 to +31

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# application-prod.yml 파일이 존재하는지 확인하고 내용 읽기
if [ -f "src/main/resources/application-prod.yml" ]; then
  echo "=== application-prod.yml 파일 내용 ===" 
  cat -n "src/main/resources/application-prod.yml"
else
  echo "파일을 찾을 수 없습니다."
  # 유사한 파일들 찾아보기
  echo "=== 유사한 설정 파일 찾기 ==="
  find . -name "application*.yml" -o -name "application*.yaml" 2>/dev/null | head -20
fi

Repository: Miche-Let/inventory-service

Length of output: 2188


🏁 Script executed:

# 프로젝트 구조 확인
echo "=== 프로젝트 구조 확인 ===" 
ls -la src/main/resources/ 2>/dev/null || echo "resources 디렉토리 없음"

Repository: Miche-Let/inventory-service

Length of output: 550


🏁 Script executed:

# 다른 환경 파일들의 ddl-auto 설정 확인
echo "=== application-docker.yml 중 ddl-auto 설정 ===" 
grep -A 2 "ddl-auto" src/main/resources/application-docker.yml 2>/dev/null || echo "설정 없음"

echo -e "\n=== application-local.yml 중 ddl-auto 설정 ===" 
grep -A 2 "ddl-auto" src/main/resources/application-local.yml 2>/dev/null || echo "설정 없음"

echo -e "\n=== application-perf.yml 중 ddl-auto 설정 ===" 
grep -A 2 "ddl-auto" src/main/resources/application-perf.yml 2>/dev/null || echo "설정 없음"

echo -e "\n=== application.yml 중 ddl-auto 설정 ===" 
grep -A 2 "ddl-auto" src/main/resources/application.yml 2>/dev/null || echo "설정 없음"

Repository: Miche-Let/inventory-service

Length of output: 366


🏁 Script executed:

# Hibernate 프로젝트 설정에서 JPA/Hibernate 버전 확인
echo "=== Maven/Gradle 설정에서 Hibernate 버전 확인 ===" 
if [ -f "pom.xml" ]; then
  grep -i "hibernate\|spring-data-jpa\|spring-boot" pom.xml | head -20
elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
  grep -i "hibernate\|spring-data-jpa\|spring-boot" build.gradle* | head -20
fi

Repository: Miche-Let/inventory-service

Length of output: 795


프로덕션 환경에서 ddl-auto: update 설정을 validate로 변경해야 합니다.

런타임에 자동으로 스키마를 변경하면 예기치 않은 드리프트와 장애가 발생할 수 있습니다. Docker 환경에서는 이미 validate를 사용 중이므로, 프로덕션도 동일하게 설정하고 명시적 마이그레이션으로 관리하는 것이 안전합니다.

수정 예시
     jpa:
         hibernate:
-            ddl-auto: update
+            ddl-auto: validate
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
jpa:
hibernate:
ddl-auto: update
jpa:
hibernate:
ddl-auto: validate
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/application-prod.yml` around lines 29 - 31, 현재
application-prod.yml의 jpa.hibernate.ddl-auto가 update로 설정되어 있어 프로덕션에서 스키마가 런타임에
변경될 수 있으니 값을 validate로 변경하세요; application-prod.yml 파일에서 jpa.hibernate.ddl-auto:
update를 jpa.hibernate.ddl-auto: validate로 바꾸고, 데이터베이스 스키마 변경은 Flyway/ Liquibase
같은 명시적 마이그레이션 도구로 관리하도록 관련 배포 문서나 CI/CD 파이프라인을 업데이트하여 프로덕션과 Docker 환경이 동일한 검증
방식을 사용하도록 보장하세요.

properties:
hibernate:
default_schema: inventory_service
format_sql: true
show-sql: true

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

find . -name "application-prod.yml" -type f

Repository: Miche-Let/inventory-service

Length of output: 113


🏁 Script executed:

cat -n ./src/main/resources/application-prod.yml

Repository: Miche-Let/inventory-service

Length of output: 2153


🌐 Web query:

Spring Boot show-sql production security best practice

💡 Result:

In production environments, you should always set spring.jpa.show-sql=false [1][2]. Enabling this property is considered a security and operational anti-pattern for several reasons [3][2][4]. Why spring.jpa.show-sql is not suitable for production: 1. Security Risk: It can inadvertently expose sensitive data, database schema details, or internal logic in your application logs [2]. 2. Performance Impact: It writes directly to standard output (System.out) without the optimizations, filtering, or asynchronous handling provided by professional logging frameworks [3][5][4]. This can cause significant performance degradation under heavy load [2]. 3. Log Noise: It generates excessive, unformatted output that fills up log files rapidly, making it difficult to identify actual application errors or security incidents [2][4]. 4. Limited Utility: It only displays the prepared statement structure (with question marks for parameters), which is often insufficient for debugging complex issues [2]. Recommended Best Practices: If you need to inspect SQL queries for troubleshooting, use the following approaches instead of show-sql: 1. Use Logging Frameworks: Configure your logging framework (e.g., Logback) to capture SQL at the appropriate level. This allows you to route logs to files, filter them, and manage them according to your organization's retention policies [5][4]. - To log SQL statements: Set logging.level.org.hibernate.SQL=DEBUG [5][6][4]. - To log parameter values: Set logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE [5][2][6][4]. 2. Use Spring Profiles: Always restrict these logging configurations to non-production profiles (e.g., dev or test) using Spring Profiles [2]. Ensure your production configuration explicitly disables them [1]. 3. Use Interceptors (Advanced): For more robust or customized logging, consider using a library like DataSource-Proxy [3][5]. This allows you to intercept JDBC calls, format them, and log them consistently without relying on Hibernate-specific properties [3][5]. Security Note: While logging SQL is a debugging tool, it is not a substitute for proper security measures. Always protect against SQL injection by using parameterized queries (which Spring Data JPA does automatically when using standard repository methods or @Query with named/indexed parameters) and avoiding manual string concatenation in queries [7][8][9].

Citations:


🌐 Web query:

Spring Boot management.endpoint.health.show-details production security best practice

💡 Result:

In production environments, setting management.endpoint.health.show-details to always is generally considered a security risk and is not recommended [1][2][3]. The health endpoint is often exposed to monitoring systems (like Kubernetes liveness/readiness probes) that may not be authenticated [1][4][5]. If show-details is set to always, the endpoint may reveal sensitive internal information—such as database connection details, disk space paths, or other infrastructure-specific data—to anyone who can access the endpoint [1][3][4]. Best Practices for Production: 1. Use when-authorized: Instead of always, set management.endpoint.health.show-details to when-authorized [1][2]. This ensures that detailed health information is only visible to authenticated users with appropriate roles, while unauthenticated users (or monitoring tools) receive only the basic status [1][2]. 2. Configure Roles: When using when-authorized, you can define which roles are permitted to see the details using the management.endpoint.health.roles property [1][2]. 3. Secure Actuator Endpoints: Always protect your Actuator endpoints using Spring Security [6][7]. If you must expose the health endpoint for monitoring, ensure your security configuration explicitly permits access to the basic health status for those monitoring tools while restricting access to the detailed information [1][2][6]. 4. Limit Exposure: Only expose the endpoints you absolutely need [6][7]. Use management.endpoints.web.exposure.include to explicitly list required endpoints (e.g., health, info, prometheus) rather than using a wildcard (*) [6][4][7]. 5. Use a Separate Management Port: For enhanced security, consider running Actuator on a separate management port (management.server.port) that is not publicly accessible and can be protected by a firewall [6]. If you have secured your application and specifically require the always setting, ensure your security configuration explicitly permits access to the health endpoint for both authenticated and unauthenticated users, acknowledging the potential information disclosure [1][2].

Citations:


운영 환경의 SQL 및 헬스 엔드포인트에서 민감 정보 노출

show-sql: true는 SQL 쿼리와 데이터베이스 스키마를 로그에 노출시키며, 성능 저하를 유발합니다. show-details: always는 인증되지 않은 사용자도 데이터베이스 연결 정보와 내부 인프라 정보에 접근할 수 있게 합니다. 두 설정 모두 프로덕션 환경에서 비활성화해야 합니다.

수정 예시
-        show-sql: true
+        show-sql: false
@@
         health:
-            show-details: always
+            show-details: when-authorized
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/application-prod.yml` at line 36, Change the production
YAML to stop logging SQL and exposing health details: set show-sql to false
(replace the current show-sql: true) and add or update the
management.endpoint.health.show-details property to a restrictive value like
never (or when-authorized) so health endpoint details are not exposed publicly;
target the settings shown as show-sql and
management.endpoint.health.show-details in application-prod.yml.


cloud:
inetutils:
ignored-interfaces: [ "lo", "docker.*", "veth.*" ]
preferred-networks: [ "10.0" ]
use-only-site-local-interfaces: true

eureka:
client:
enabled: ${EUREKA_ENABLED:false}
service-url:
defaultZone: http://${EUREKA_HOST}:${EUREKA_PORT}/eureka/
instance:
prefer-ip-address: true

management:
endpoints:
web:
exposure:
include: health,info,prometheus
endpoint:
health:
show-details: always
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,38 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.transaction.annotation.Transactional;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@ActiveProfiles("test")
@Transactional
@Testcontainers
class StockSchedulerServiceTest {

// 테스트 실행 시 가짜 미니 Redis 컨테이너 띄우기 (인프라 환경과 동일한 버전)
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);

// 띄워진 가짜 Redis의 동적 IP와 포트를 스프링 환경변수에 주입
@DynamicPropertySource
static void redisProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.redis.host", redis::getHost);
registry.add("spring.data.redis.port", () -> redis.getMappedPort(6379));
}

// Kafka 연결 에러(로그 도배 및 실패)를 막기 위해 가짜 템플릿 주입
@MockitoBean
private KafkaTemplate<String, String> kafkaTemplate;

@Autowired
private StockSchedulerService stockSchedulerService;

Expand Down
Loading