AWESOMEDEV 0e3ab14b14
All checks were successful
CI / backend-test (push) Successful in 1m2s
CI / frontend-build (push) Successful in 23s
CI / backend-dep-scan (push) Successful in 27s
fix(security): Spring Boot 3.4.2 → 3.5.16 (OSV 발견 CVE 전부 해결)
OSV-Scanner가 찾은 누적 CVE를 관리 의존성 버전 상향으로 일괄 해결:
- tomcat-embed-core 10.1.34 → 10.1.55 (CVSS 9.8 포함 다수)
- spring-security-* 6.4.2 → 6.5.11 (CVSS 9.1 포함)
- spring-web/webmvc/core/context 6.2.2 → 6.2.19
- assertj-core 3.26.3 → 3.27.7
취약 의존성 전부 OSV 수정버전 이상으로 확인(dependency:tree 대조).

현재 GA 라인(3.5.x) 최신 패치라 지원 수명도 길다. 표준 스타터만 써서
마이너 상향에도 회귀 없음: 전체 47개 테스트 통과.
운영 검증: 세션 로그인 200·멘토 권한 200·감사쓰기(실 IP)·비로그인 401·스모크 11/11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 07:27:35 +09:00

7.2 KiB

CI — Gitea Actions (push 시 자동 테스트)

push/PR마다 백엔드 테스트 + 프론트 빌드를 자동으로 돌려, 회귀를 배포 전에 잡는다. 워크플로 정의: .gitea/workflows/ci.yml

구성 요소

요소 위치 역할
Actions 활성화 docker-compose.prod.yml gitea env GITEA__actions__ENABLED=true Gitea에 Actions 기능 켜기
러너(runner) compose 서비스 runner(gitea/act_runner) 일감을 받아 job 컨테이너를 띄우는 일꾼
러너 설정·등록 호스트 /opt/mirim-runner/ (config.yaml, .runner) 최초 등록 정보·job 컨테이너 네트워크 설정
워크플로 .gitea/workflows/ci.yml 무엇을 검사할지(백엔드 test, 프론트 build)

동작 흐름

  1. 누가 push하면 Gitea가 워크플로를 큐에 넣는다.
  2. 러너가 job을 받아 catthehacker/ubuntu:act-22.04 이미지로 job 컨테이너를 띄운다.
  3. job 컨테이너는 deploy_default 네트워크 + --add-host edu.awesomedevapp.com:host-gateway로 Gitea에서 코드를 clone한 뒤, 백엔드 mvn test / 프론트 npm ci && npm run build를 실행한다.
  4. 하나라도 실패하면 커밋에 빨간 X, 다 통과하면 초록 체크가 붙는다. (Gitea 웹 → Actions 탭)

러너를 처음부터 다시 세워야 한다면

# 1) Actions 활성화 (compose env, 이미 되어 있음) 후 gitea 재기동
# 2) 등록 토큰 발급
sudo docker exec -u git mirim-gitea gitea --config /data/gitea/conf/app.ini actions generate-runner-token

# 3) 설정 파일 (/opt/mirim-runner/config.yaml)
#    labels: ubuntu-latest → catthehacker/ubuntu:act-22.04
#    container.network: deploy_default
#    container.options: --add-host=edu.awesomedevapp.com:host-gateway   ← job이 Gitea에 clone하려면 필수
# 4) 최초 1회만 토큰으로 등록 (등록되면 .runner 생성, 이후 compose가 관리)
sudo docker run --rm --network deploy_default \
  -v /var/run/docker.sock:/var/run/docker.sock -v /opt/mirim-runner:/data -w /data \
  -e GITEA_INSTANCE_URL=http://mirim-gitea:3000 \
  -e GITEA_RUNNER_REGISTRATION_TOKEN=<토큰> -e CONFIG_FILE=/data/config.yaml \
  gitea/act_runner:0.2.11
# 5) compose로 상시 실행
sudo docker compose -f deploy/docker-compose.prod.yml up -d runner

함정 메모 (겪은 것)

  • Maven 다운로드: dlcdn 미러는 옛 버전을 지운다(3.9.9가 404로 사라져 CI가 깨졌었다). → 모든 버전을 영구 보관하는 archive.apache.org를 쓴다.
  • clone 실패: job 컨테이너가 Gitea(edu.awesomedevapp.com:3000)에 못 닿으면 checkout이 실패한다. → --add-host edu.awesomedevapp.com:host-gateway로 컨테이너에서 호스트(→ 게시된 3000)로 잇는다.
  • actions/checkout은 node 필요: job 이미지에 node가 있어야 한다(catthehacker 이미지에 포함). 그래서 백엔드 job도 이 이미지를 쓰고 Maven만 따로 내려받는다.

배포 스모크 테스트 (deploy/smoke-test.sh)

CI는 "코드"를 배포 전에 검사한다. 하지만 실제로 났던 사고(보안헤더로 학습 문서 iframe이 안 뜬 것)는 코드가 아니라 Caddy 설정 문제라 CI 테스트로는 안 잡혔다. 스모크 테스트는 배포된 살아있는 사이트를 실제 사용자처럼 두드려, 설정·헤더·인프라까지 포함해 "정말 열리는가"를 확인한다. → CI(코드 그물) + 스모크(설정·운영 그물).

# 배포 직후 반드시 실행 — 하나라도 실패하면 exit 1
bash deploy/smoke-test.sh

검사 항목(11): 홈 200 · HTTP→HTTPS 리다이렉트 · 보안헤더 3종 · 학습 문서 200 + X-Frame-Options=SAMEORIGIN + no-cache + 한국어 본문(그 회귀를 직접 감지) · 백엔드 API 401(Caddy→백엔드→DB 경로) · SPA 라우트 폴백.

권장 배포 흐름: 빌드 → 업로드 → docker compose upbash deploy/smoke-test.sh. 스모크가 빨간불이면 배포에 문제가 있는 것이니 바로 롤백/수정한다.

품질 게이트 (push/PR마다 자동 검사)

CI가 통과해야 하는 관문이 아래처럼 늘었다. 로컬에서 먼저 돌려 보고 밀면 빨간불을 피한다.

게이트 무엇을 막나 로컬에서 고치는 법
Spotless(백엔드) 안 쓰는 import·줄끝 공백·파일끝 개행 cd backend && mvn spotless:apply
JaCoCo(백엔드) 라인 커버리지 30% 미만으로 후퇴 테스트 추가 (mvn testtarget/site/jacoco/index.html)
ESLint(프론트) 안 쓰는 변수·없는 전역·React 훅 오용 cd frontend && npm run lint
커버리지(프론트) 채점 로직 runSql.js 40% 미만 npm run coverage 로 확인 후 테스트 보강

커버리지 바닥(30%/40%)은 "지금보다 나빠지지 마라"는 **래칫(ratchet)**이다. 테스트를 늘리면 이 숫자도 함께 올려 바닥을 끌어올리는 게 건강한 흐름.

의존성 취약점 스캔 (라이브러리 CVE)

우리가 직접 쓴 코드뿐 아니라 "가져다 쓰는 라이브러리"에도 취약점이 생긴다. CI가 이를 훑는다.

대상 도구 방식
프론트(npm) npm audit --omit=dev --audit-level=high 온디맨드(대용량 DB 없음)
백엔드(Maven) OSV-Scanner v2.4.0, scan source -r backend osv.dev 온디맨드 질의

둘 다 비차단(continue-on-error)으로 "보고 전용" 시작한다. 이유: 이 스캔은 "코드"가 아니라 "외부 권고 DB 상태"에 결합돼, 우리가 한 줄도 안 고쳐도 새 CVE가 공개되면 차단형이면 무관한 PR까지 전부 CI가 붉어진다. 팀이 수용목록(allow-list) 운영에 익숙해지면 차단으로 승격한다.

첫 스캔이 값어치를 증명하고, 바로 해결됐다: 백엔드 OSV 스캔이 Spring Boot 3.4.2의 누적 CVE(tomcat-embed-core·spring-security-* 등, 일부 CVSS 9점대)를 다수 찾아냈다. Spring Boot 3.4.2 → 3.5.16 업그레이드로 전부 해결(tomcat 10.1.34→10.1.55, spring-security 6.4.2→6.5.11, spring-web 6.2.2→6.2.19 등 취약 의존성 모두 수정버전 이상). "스캔으로 찾고 → 버전 상향으로 고치고 → 재스캔/테스트로 확인"의 한 사이클 예시.

main 브랜치 보호 (PR 워크플로)

main은 보호 규칙이 걸려 있다(Gitea → 저장소 설정 → 브랜치):

  • CI 두 개(backend-test·frontend-build)가 초록불이어야 merge 가능
  • PR에 승인 1개 필요 (멘토가 학생 PR을 리뷰·승인)
  • 학생은 main에 직접 push 불가 → 브랜치 만들어 PR 여는 흐름을 익힌다
  • 운영·배포용으로 관리자 계정만 직접 push 허용(화이트리스트)

학생 실습 흐름: git switch -c feat/내작업 → 커밋 → push → Gitea에서 PR 생성 → CI 초록불 + 멘토 승인 → merge. (실무 표준 흐름을 그대로 경험)

다음 개선 (학생 티켓)

  • Maven·npm 캐시로 CI 실행 시간 단축
  • 커버리지 바닥을 단계적으로 상향(30%→40%→…)
  • 스모크 테스트를 배포 스크립트에 자동 편입(deploy 후 자동 실행·실패 시 알림)
  • E2E 테스트(Playwright: 로그인→강좌→퀴즈)