Skip to content

[BE] ACG Rule을 활용한 Secure CI CD 파이프라인 구현

박진명 edited this page Dec 3, 2024 · 1 revision

프로젝트를 시작하고 원활한 협업과 배포를 진행하기 위해 CI/CD 파이프라인을 먼저 구현하기 시작하였지만 얼마 지나지 않아 아래와 같은 메일을 받았습니다.

image

github action에서 구축한 docker 를 NCP 환경에서 pull 하기 위해 ssh 연결을 하는 수행하고 있었고 이를 위해 모든 IP의 22번 포트 (0.0.0.0)를 열어 두었던 게 화근이 되어 비정상적인 접근이 발생하는 것을 확인하였습니다.

이에 서버 보안을 강화하기 위해 모든 IP에 대해 22번 포트를 열어 두는 대신 Github Actions 의 IP를 동적으로 관리하는 방식으로 CI/CD 파이프라인을 구현해보았습니다.

보안 강화 방식

  1. Github Action Runner IP 확인
    • haythem/public-ip 액션을 사용하여 현재 실행 중인 GitHub Actions runner의 IP 주소를 동적으로 확인
    • 이 IP는 배포 과정에서만 임시로 접근을 허용
  2. NCP (Naver Cloud Platform) ACG 규칙 관리
    • 배포 시작 시 Runner IP를 ACG 인바운드 규칙에 추가
    • 배포 완료 후 해당 IP를 자동으로 제거
    • SSH 포트만 선별적으로 개방하여 보안 강화

작성한 WorkFlow

name: deploy

on:
  push:
    branches: [dev]
  pull_request:
    branches: [dev]

env:
  DOCKER_IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/juga-docker
  DOCKER_TAG: ${{ github.sha }}

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    strategy:
      max-parallel: 1
      matrix:
        app:
          [
            {
              name: 'be-1',
              dir: 'BE',
              port: 3000,
              container: 'juga-docker-be-1',
              env: 'ENV_BE_1',
            },
            {
              name: 'be-2',
              dir: 'BE',
              port: 3001,
              container: 'juga-docker-be-2',
              env: 'ENV_BE_2',
            },
            {
              name: 'be-3',
              dir: 'BE',
              port: 3002,
              container: 'juga-docker-be-3',
              env: 'ENV_BE_3',
            },
            {
              name: 'fe',
              dir: 'FE',
              port: 5173,
              container: 'juga-docker-fe',
              env: 'ENV_FE',
            },
          ]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: ./${{matrix.app.dir}}/package-lock.json

      - name: Create .env file
        run: |
          touch ./${{ matrix.app.dir }}/.env
          echo "${{ secrets[matrix.app.env] }}" > ./${{matrix.app.dir}}/.env

      - name: Install dependencies
        working-directory: ./${{matrix.app.dir}}
        continue-on-error: true
        run: npm ci

      - name: Run tests
        if: ${{ contains(matrix.app.name, 'be') }}
        working-directory: ./${{matrix.app.dir}}
        run: npm test
        env:
          CI: true

      - name: Run linter
        working-directory: ./${{matrix.app.dir}}
        run: npm run lint

      - name: Build application
        working-directory: ./${{matrix.app.dir}}
        run: npm run build

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ vars.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and Push Docker Image
        working-directory: ./${{ matrix.app.dir }}
        env:
          NCP_ACCESS_KEY: ${{ secrets.NCP_ACCESS_KEY }}
          NCP_SECRET_KEY: ${{ secrets.NCP_SECRET_KEY }}
        run: |
          if [ "${{ matrix.app.name }}" = "fe" ]; then
            docker build \
              --build-arg VITE_SOCKET_URL=${{ secrets.VITE_SOCKET_URL }} \
              --build-arg VITE_API_URL=${{ secrets.VITE_API_URL }} \
              --build-arg VITE_API_KAKAO_URL=${{ secrets.VITE_API_KAKAO_URL }} \
              --build-arg VITE_STORAGE_KEY=${{ secrets.VITE_STORAGE_KEY }} \
              --build-arg VITE_MAX_HISTORY_ITEMS=${{ secrets.VITE_MAX_HISTORY_ITEMS }} \
              -t ${{ env.DOCKER_IMAGE }}-${{ matrix.app.name }}:${{ env.DOCKER_TAG }} .
          else
            docker build -t ${{ env.DOCKER_IMAGE }}-${{ matrix.app.name }}:${{ env.DOCKER_TAG }} .
          fi

          docker tag ${{ env.DOCKER_IMAGE }}-${{ matrix.app.name }}:${{ env.DOCKER_TAG }} ${{ env.DOCKER_IMAGE }}-${{ matrix.app.name }}:latest

          docker push ${{ env.DOCKER_IMAGE }}-${{ matrix.app.name }}:${{ env.DOCKER_TAG }}
          docker push ${{ env.DOCKER_IMAGE }}-${{ matrix.app.name }}:latest

      - name: Get Github Actions IP
        id: ip
        uses: haythem/public-ip@v1.2

      - name: Setting NCP CLI & Credentials
        run: |
          cd ~
          wget https://www.ncloud.com/api/support/download/5/65
          unzip 65
          mkdir ~/.ncloud
          echo -e "[DEFAULT]\nncloud_access_key_id = ${{ secrets.NCP_ACCESS_KEY }}\nncloud_secret_access_key = ${{ secrets.NCP_SECRET_KEY }}\nncloud_api_url = ${{ secrets.NCP_API_URI }}" >> ~/.ncloud/configure

      - name: Add Github Action Ip to Security group
        run: |
          cd ~
          ls -la
          chmod -R 777 ~/cli_linux
          cd ~/cli_linux
          ./ncloud vserver addAccessControlGroupInboundRule --regionCode KR --vpcNo ${{ secrets.NCP_VPC_ID }} --accessControlGroupNo ${{ secrets.NCP_ACG_ID }} --accessControlGroupRuleList "protocolTypeCode='TCP', ipBlock='${{ steps.ip.outputs.ipv4 }}/32', portRange='${{ secrets.SSH_PORT }}'"

      - name: Deploy to NCP Server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.NCP_SERVER_HOST }}
          username: ${{ secrets.NCP_SERVER_USERNAME }}
          key: ${{ secrets.NCP_SERVER_SSH_KEY }}
          port: 22
          script: |
            docker system prune -af

            echo "${{ secrets[matrix.app.env] }}" > ${{matrix.app.name}}.env
            docker network create juga-network || true

            if [ "${{ matrix.app.name }}" = "be-1" ]; then
              docker run -d \
                --name redis \
                --network juga-network \
                -p 6379:6379 \
                -v redis_data:/data \
                redis:latest redis-server --appendonly yes
            fi

            docker pull ${{ env.DOCKER_IMAGE }}-${{ matrix.app.name }}:${{ env.DOCKER_TAG }}
            docker stop ${{ matrix.app.container }} || true
            docker rm ${{ matrix.app.container }} || true
            docker run -d \
              --name ${{ matrix.app.container }} \
              --network juga-network \
              -p ${{ matrix.app.port }}:3000 \
              --env-file ${{matrix.app.name}}.env \
              ${{ env.DOCKER_IMAGE }}-${{ matrix.app.name }}:${{ env.DOCKER_TAG }}

            docker ps | grep ${{ matrix.app.container }}
            docker logs ${{ matrix.app.container }}
            rm ${{matrix.app.name}}.env

      - name: Remove Github Action Ip to Security group
        run: |
          chmod -R 777 ~/cli_linux
          cd ~/cli_linux
          ./ncloud vserver removeAccessControlGroupInboundRule --regionCode KR --vpcNo ${{ secrets.NCP_VPC_ID }} --accessControlGroupNo ${{ secrets.NCP_ACG_ID }} --accessControlGroupRuleList "protocolTypeCode='TCP', ipBlock='${{ steps.ip.outputs.ipv4 }}/32', portRange='${{ secrets.SSH_PORT }}'"

📜 개발 일지

⚠️ 트러블 슈팅

❗ 규칙

🗒️ 기록

기획
회의록
데일리스크럼
그룹 멘토링
그룹 회고

😲 개별 멘토링

고동우
김진
서산
이시은
박진명
Clone this wiki locally