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
176 changes: 176 additions & 0 deletions .github/actions/deploy-module/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
name: 'Deploy Module'
description: 'Build, push and deploy a module to server (dev/prod)'

inputs:
environment:
description: 'Environment (dev or prod)'
required: true
module:
description: 'Module name (apis, admin, batch)'
required: true
port:
description: 'Server port (for logging only - actual port defined in docker-compose.yml)'
required: false
default: 'N/A'
dockerhub-username:
description: 'Docker Hub username'
required: true
dockerhub-token:
description: 'Docker Hub token'
required: true
secret-properties:
description: 'Secret properties (dev or prod)'
required: true
apple-auth-key:
description: 'Apple Auth Key'
required: true
host:
description: 'Server host'
required: true
username:
description: 'Server username'
required: true
ssh-key:
description: 'Server SSH key'
required: true
ssh-port:
description: 'Server SSH port'
required: true
discord-webhook-url:
description: 'Discord webhook URL'
required: true
image-prefix:
description: 'Docker image prefix'
required: true
image-tag-type:
description: 'Image tag type (development-latest or semver)'
required: true
deploy-script:
description: 'Deploy script name (deploy-dev.sh or deploy-prod.sh)'
required: true
default: 'deploy-dev.sh'
Comment on lines +48 to +51
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

deploy-script 입력 설명 명확화 제안

라인 51의 기본값 'deploy-dev.sh'는 설명과 실제 사용이 혼동을 줄 수 있습니다. 의존 워크플로우(dev-ci-cd.yml, prod-ci-cd.yml)에서 명시적으로 deploy-dev.sh 또는 deploy-prod.sh를 전달하고 있으므로, 설명을 다음과 같이 개선하면 좋습니다:

deploy-script:
  description: 'Deploy script name (deploy-dev.sh for dev, deploy-prod.sh for prod) - 워크플로우에서 명시적으로 전달됨'
  required: true
  default: 'deploy-dev.sh'

이렇게 하면 향후 독자가 기본값이 개발 환경 용도임을 알 수 있고, 워크플로우에서 의도적으로 override됨을 이해할 수 있습니다.

🤖 Prompt for AI Agents
.github/actions/deploy-module/action.yml around lines 48 to 51: the input
description for `deploy-script` is ambiguous given the default value
'deploy-dev.sh' and workflows explicitly pass either deploy-dev.sh or
deploy-prod.sh; update the description to state that the default is for dev and
that workflows explicitly override it (e.g., "Deploy script name (deploy-dev.sh
for dev, deploy-prod.sh for prod) - workflows explicitly override this value"),
keep required: true and default: 'deploy-dev.sh' unchanged.


runs:
using: 'composite'
steps:
- name: Inject application-secret.properties from Secrets
shell: bash
run: |
mkdir -p ./secret
echo "$SECRET_CONTENT" > ./secret/application-${{ inputs.environment }}-secret.properties
echo "$APPLE_KEY_CONTENT" > ./secret/AuthKey.p8
chmod 600 ./secret/*
env:
SECRET_CONTENT: ${{ inputs.secret-properties }}
APPLE_KEY_CONTENT: ${{ inputs.apple-auth-key }}
Comment on lines +56 to +65
Copy link

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

🔴 기밀정보 보안: 런타임 시크릿 마운트로 마이그레이션 필수

현재 구현은 GitHub Actions 런너에 기밀정보 파일을 작성한 후 Docker 빌드 컨텍스트에 포함시켜 최종 이미지 레이어에 영구적으로 저장합니다. 빌드 인자와 환경 변수는 최종 이미지에 지속되므로 부적절하며, 대신 시크릿 마운트나 SSH 마운트를 사용해야 합니다.

필수 수정 사항:

  1. docker/build-push-action@v6 단계에 secrets 매개변수 추가 (라인 83-95):
secrets: |
  "application_secret=${{ inputs.secret-properties }}"
  "apple_auth_key=${{ inputs.apple-auth-key }}"
  1. Dockerfile 업데이트: RUN --mount=type=secret 플래그를 사용하여 시크릿을 빌드 컨테이너 내 /run/secrets/<id> 경로에 임시로 마운트합니다.

  2. 기밀정보 파일 작성 단계 제거 (라인 56-65): 런타임에 시크릿을 주입하도록 변경하면, ./secret 디렉토리 생성이 불필요해집니다.

BuildKit은 tmpfs를 사용하여 시크릿을 임시 파일로 마운트하므로 이미지에 시크릿이 남지 않습니다.


시크릿 보안 문제를 검증하기 위해 필요한 정보를 수집하겠습니다.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find and examine the action.yml file
echo "=== Examining action.yml ==="
find . -name "action.yml" -path "*deploy-module*" -type f | head -5

Length of output: 71


🏁 Script executed:

#!/bin/bash
# Look for Dockerfile in the repository
echo "=== Finding Dockerfile ==="
fd -t f -i dockerfile | head -10

Length of output: 37


🏁 Script executed:

#!/bin/bash
# Search for docker/build-push-action usage
echo "=== Searching for docker/build-push-action usage ==="
rg -n "build-push-action" --type yaml -A 10 -B 2 | head -50

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Check for secrets parameter in build-push-action or any secret handling
echo "=== Checking for secrets parameter in workflows ==="
rg -n "secrets:" --type yaml -A 5 -B 5 | head -80

Length of output: 51


🏁 Script executed:

#!/bin/bash
# Examine the specific action.yml file
echo "=== Full action.yml content ==="
cat ".github/actions/deploy-module/action.yml" 2>/dev/null || echo "File not found at standard path"

Length of output: 6582


Dockerfile를 검증하여 시크릿 처리 방식을 확인하겠습니다.


---

🏁 Script executed:

```shell
#!/bin/bash
# Examine the Dockerfile
echo "=== Examining Dockerfile ==="
cat ./Dockerfile

Length of output: 1310


🏁 Script executed:

#!/bin/bash
# Search for secret-related patterns in Dockerfile
echo "=== Checking for ./secret references in Dockerfile ==="
grep -n "secret\|SECRET" ./Dockerfile || echo "No secret references found"

Length of output: 124


🏁 Script executed:

#!/bin/bash
# Check for RUN --mount=type=secret pattern
echo "=== Checking for secret mount patterns ==="
grep -n "mount=type=secret" ./Dockerfile || echo "No secret mount patterns found"

Length of output: 73


🏁 Script executed:

#!/bin/bash
# Check if .dockerignore exists and what it contains
echo "=== Checking .dockerignore ==="
cat ./.dockerignore 2>/dev/null || echo ".dockerignore not found"

Length of output: 54


🔴 중대 보안 이슈: 시크릿이 Docker 이미지 레이어에 영구 저장됨

현재 구현은 시크릿 데이터가 최종 Docker 이미지에 영구적으로 저장되는 심각한 취약점을 가지고 있습니다. 검증 결과:

  • action.yml (56-65줄): ./secret 디렉토리에 시크릿 파일 작성
  • Dockerfile (36줄): COPY --from=build /app/secret ./secret/ 명령으로 시크릿을 최종 이미지로 복사
  • docker/build-push-action@v6 (83-95줄): secrets 파라미터 부재 → 시크릿이 빌드 컨텍스트에 노출
  • .dockerignore: 존재하지 않음 → ./secret 폴더 제외 불가능

결과적으로 시크릿이 Docker Hub에 푸시된 이미지 레이어에 평문으로 저장됩니다.

필수 수정 사항:

  1. docker/build-push-action@v6에 secrets 파라미터 추가 (83-95줄):

    with:
      context: .
      file: ./Dockerfile
      platforms: linux/amd64
      push: true
      tags: ${{ steps.meta.outputs.tags }}
      cache-from: type=gha,scope=${{ inputs.module }}
      cache-to: type=gha,mode=max,scope=${{ inputs.module }}
      build-args: |
        MODULE=${{ inputs.module }}
      secrets: |
        "application_secret=${{ inputs.secret-properties }}"
        "apple_auth_key=${{ inputs.apple-auth-key }}"
  2. Dockerfile 업데이트 (36줄 제거 및 시크릿 마운트 사용):

    • 기존: COPY --from=build /app/secret ./secret/ 제거
    • 변경: RUN --mount=type=secret=application_secret --mount=type=secret=apple_auth_key 사용하여 런타임에 /run/secrets/ 경로에서 접근
  3. action.yml의 시크릿 파일 작성 단계 제거 또는 조건부 처리 (56-65줄):

    • BuildKit 시크릿 마운트 사용 시 ./secret 디렉토리 생성 불필요


- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ inputs.dockerhub-username }}
password: ${{ inputs.dockerhub-token }}

- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: docker.io/${{ inputs.image-prefix }}-${{ inputs.module }}
tags: ${{ inputs.image-tag-type }}

- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha,scope=${{ inputs.module }}
cache-to: type=gha,mode=max,scope=${{ inputs.module }}
build-args: |
MODULE=${{ inputs.module }}
- name: Deploy to Server
uses: appleboy/[email protected]
with:
host: ${{ inputs.host }}
username: ${{ inputs.username }}
key: ${{ inputs.ssh-key }}
port: ${{ inputs.ssh-port }}
script: |
export DOCKERHUB_USERNAME="${{ inputs.dockerhub-username }}"
export DOCKERHUB_TOKEN="${{ inputs.dockerhub-token }}"
export MODULE="${{ inputs.module }}"
export SPRING_PROFILE="${{ inputs.environment }}"
export IMAGE_TAG="$(echo "${{ steps.meta.outputs.tags }}" | head -n1)"
cd ~/deploy
chmod +x ./${{ inputs.deploy-script }}
./${{ inputs.deploy-script }}
- name: Send Discord notification on success (Development)
uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645
if: success() && inputs.environment == 'dev'
continue-on-error: true
shell: bash
with:
webhook-url: ${{ inputs.discord-webhook-url }}
embed-title: "✅ [${{ github.repository }}] Development Deploy Succeeded - ${{ inputs.module }}"
embed-description: |
**Module**: `${{ inputs.module }}`
**Commit**: `${{ github.sha }}`
**Author**: `${{ github.actor }}`
**Message**: `${{ github.event.head_commit.message }}`
[View Committed Changes](https://github.com/${{ github.repository }}/commit/${{ github.sha }})
embed-color: 65280

- name: Send Discord notification on success (Production)
uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645
if: success() && inputs.environment == 'prod'
continue-on-error: true
shell: bash
with:
webhook-url: ${{ inputs.discord-webhook-url }}
content: "🚀 **Production Deploy Succeeded!**"
embed-title: "✅ [${{ github.repository }}] Production Deploy Succeeded - ${{ inputs.module }}"
embed-description: |
**Module**: `${{ inputs.module }}`
**Deployed by**: `${{ github.actor }}`
The new version has been successfully deployed to production.
[View Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
embed-color: 65280

- name: Send Discord notification on failure (Development)
uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645
if: failure() && inputs.environment == 'dev'
continue-on-error: true
shell: bash
with:
webhook-url: ${{ inputs.discord-webhook-url }}
embed-title: "❌ [${{ github.repository }}] Development Deploy Failed - ${{ inputs.module }}"
embed-description: |
**Module**: `${{ inputs.module }}`
**Commit**: `${{ github.sha }}`
**Author**: `${{ github.actor }}`
An error occurred during the workflow execution.
[View Failed Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
embed-color: 16711680

- name: Send Discord notification on failure (Production)
uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645
if: failure() && inputs.environment == 'prod'
continue-on-error: true
shell: bash
with:
webhook-url: ${{ inputs.discord-webhook-url }}
content: "🚨 **Production Deploy Failed!**"
embed-title: "❌ [${{ github.repository }}] Production Deploy Failed - ${{ inputs.module }}"
embed-description: |
**Module**: `${{ inputs.module }}`
**Deployed by**: `${{ github.actor }}`
An error occurred during the production deployment workflow.
[View Failed Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
embed-color: 16711680
Comment on lines +114 to +176
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Discord 알림 구성이 적절하며 선택적 개선 가능

환경별 성공/실패 알림이 명확하게 구분되어 있고, continue-on-error: true로 설정되어 웹훅 실패가 배포 프로세스에 영향을 주지 않습니다.

선택사항: 코드 중복 감소

네 개의 Discord 알림 단계에서 상당한 중복이 있습니다. 조건문을 활용하여 단일 단계로 통합할 수 있습니다:

- name: Send Discord notification
  uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645
  if: always()
  continue-on-error: true
  shell: bash
  with:
    webhook-url: ${{ inputs.discord-webhook-url }}
    content: ${{ (success() && inputs.environment == 'prod') && '🚀 **Production Deploy Succeeded!**' || (failure() && inputs.environment == 'prod') && '🚨 **Production Deploy Failed!**' || '' }}
    embed-title: |
      ${{ success() && '✅' || '❌' }} [${{ github.repository }}] 
      ${{ inputs.environment == 'prod' && 'Production' || 'Development' }} Deploy 
      ${{ success() && 'Succeeded' || 'Failed' }} - ${{ inputs.module }}
    embed-description: |
      **Module**: `${{ inputs.module }}`
      ...
    embed-color: ${{ success() && 65280 || 16711680 }}

그러나 현재 구현도 명확하고 유지보수하기 좋습니다.

🤖 Prompt for AI Agents
.github/actions/deploy-module/action.yml lines 114-176: the four nearly
identical Discord webhook steps (dev/prod × success/failure) can be consolidated
to remove duplication; replace them with a single step (keep continue-on-error:
true) that runs always() and sets webhook inputs (content, embed-title,
embed-description, embed-color) using GitHub Actions expressions that branch on
success()/failure() and inputs.environment to produce the correct text/color for
dev vs prod and success vs failure, preserving all existing fields (module,
sha/actor/run links) and ensuring empty content when not needed.

6 changes: 5 additions & 1 deletion .github/workflows/ci-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,8 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew fullCheck --parallel --build-cache --info --stacktrace
run: |
# fullCheck: 모든 모듈 (apis, admin, batch, gateway 등)의 빌드, 테스트, 정적분석 수행
# --parallel: 모듈별 병렬 빌드로 시간 단축
# --build-cache: Gradle 빌드 캐시 사용
./gradlew fullCheck --parallel --build-cache --info --stacktrace
151 changes: 72 additions & 79 deletions .github/workflows/dev-ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,96 +11,89 @@ concurrency:

env:
REGISTRY: docker.io
IMAGE_NAME: ninecraft0523/ninecraft-server
MODULE: apis
IMAGE_PREFIX: ninecraft0523/ninecraft

jobs:
build-push-and-deploy:
detect-changes:
runs-on: ubuntu-24.04
timeout-minutes: 20
environment: development

outputs:
apis: ${{ steps.filter.outputs.apis }}
# admin: ${{ steps.filter.outputs.admin }} # TODO: Uncomment when admin module is ready
batch: ${{ steps.filter.outputs.batch }}
any: ${{ steps.filter.outputs.any }}
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Inject application-secret.properties from Secrets
run: |
mkdir ./secret
echo "${{ secrets.DEV_SECRET_PROPERTIES }}" > ./secret/application-dev-secret.properties
echo "${{ secrets.APPLE_AUTH_KEY }}" > ./secret/AuthKey.p8
chmod 600 ./secret/*

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
- name: Check changed files
uses: dorny/paths-filter@v3
id: filter
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
filters: |
apis:
- 'apis/**'
- 'domain/**'
- 'infra/**'
- 'global-utils/**'
- 'observability/**'
# admin: # TODO: Uncomment when admin module is ready
# - 'admin/**'
# - 'domain/**'
# - 'infra/**'
# - 'global-utils/**'
# - 'observability/**'
batch:
- 'batch/**'
- 'domain/**'
- 'infra/**'
- 'global-utils/**'
- 'observability/**'
any:
- 'apis/**'
# - 'admin/**' # TODO: Uncomment when admin module is ready
- 'batch/**'
- 'domain/**'
- 'infra/**'
- 'global-utils/**'
- 'observability/**'

- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=development-latest
build-push-and-deploy:
needs: detect-changes
if: needs.detect-changes.outputs.any == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 20
environment: development
strategy:
fail-fast: false
matrix:
include:
- module: apis
changed: ${{ needs.detect-changes.outputs.apis }}
# - module: admin # TODO: Uncomment when admin module is ready
# changed: ${{ needs.detect-changes.outputs.admin }}
- module: batch
changed: ${{ needs.detect-changes.outputs.batch }}

- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
MODULE=${{ env.MODULE }}
steps:
- name: Checkout code
if: matrix.changed == 'true'
uses: actions/checkout@v4

- name: Deploy to Development Server
uses: appleboy/[email protected]
- name: Deploy module
if: matrix.changed == 'true'
uses: ./.github/actions/deploy-module
with:
environment: dev
module: ${{ matrix.module }}
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
secret-properties: ${{ secrets.DEV_SECRET_PROPERTIES }}
apple-auth-key: ${{ secrets.APPLE_AUTH_KEY }}
host: ${{ secrets.DEV_HOST }}
username: ${{ secrets.DEV_USERNAME }}
key: ${{ secrets.DEV_SSH_KEY }}
port: ${{ secrets.DEV_PORT }}
script: |
export DOCKERHUB_USERNAME="${{ secrets.DOCKERHUB_USERNAME }}"
export DOCKERHUB_TOKEN="${{ secrets.DOCKERHUB_TOKEN }}"
export IMAGE_TAG="$(echo "${{ steps.meta.outputs.tags }}" | head -n1)"
cd ~/deploy
chmod +x ./deploy.sh
./deploy.sh

- name: Send Discord notification on success
uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645
if: success()
continue-on-error: true
with:
webhook-url: ${{ secrets.DEV_DEPLOY_DISCORD_WEBHOOK_URL }}
embed-title: "✅ [${{ github.repository }}] Development Deploy Succeeded"
embed-description: |
**Commit**: `${{ github.sha }}`
**Author**: `${{ github.actor }}`
**Message**: `${{ github.event.head_commit.message }}`
[View Committed Changes](https://github.com/${{ github.repository }}/commit/${{ github.sha }})
embed-color: 65280

- name: Send Discord notification on failure
uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645
if: failure()
continue-on-error: true
with:
webhook-url: ${{ secrets.DEV_DEPLOY_DISCORD_WEBHOOK_URL }}
embed-title: "❌ [${{ github.repository }}] Development Deploy Failed"
embed-description: |
**Commit**: `${{ github.sha }}`
**Author**: `${{ github.actor }}`
An error occurred during the workflow execution.
[View Failed Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
embed-color: 16711680
ssh-key: ${{ secrets.DEV_SSH_KEY }}
ssh-port: ${{ secrets.DEV_PORT }}
discord-webhook-url: ${{ secrets.DEV_DEPLOY_DISCORD_WEBHOOK_URL }}
image-prefix: ${{ env.IMAGE_PREFIX }}
image-tag-type: type=raw,value=development-latest
deploy-script: deploy-dev.sh
Loading