Afterglow v1.14.0 — 2차 보안 패치 (PR-A + PR-B)
릴리스일: 2026-05-09 유형: minor (보안 강화 + 행동 변화 일부) 호환성: backward-compatible (마이그레이션 불필요, 다음 릴리스에서 v2/legacy crypto fallback 제거 예정)
요약
1차 보안 PR (Critical 5건 + High 13건 처리) 완료 후 잔존 취약점을 전수 조사한 결과 신규 Critical 5 + High 7 + Medium 11 + Low 9 가 식별됐다. 이번 릴리스는 그중 PR-A (Backend IDOR 일괄) + PR-B (K3s 보안) 두 카테고리, 총 8 commit / ~30 파일 을 처리한다.
핵심 의도:
- OpenStack RBAC 가 1차 방어선이지만 policy 가 광범위하거나 admin 토큰 누설 시 cross-tenant 노출 위험을 백엔드에서 한번 더 차단 (defense-in-depth)
- K3s 비밀 데이터 (kubeconfig / node_token / manager_password / notion) 의 단일 마스터키 의존도를 HKDF sub-key 도메인 분리로 완화
- Health Bearer 토큰의 사실상 영구화 (30일 sliding) 를 7일 절대 만료로 변경
- kubeconfig 다운로드를 forensic 으로 추적 가능하도록 audit log 추가
처리된 취약점
Critical (5건)
| ID | 항목 | 처리 | |—|—|—| | C2-1 | Object-storage 컨테이너/오브젝트 IDOR | 신규 컨테이너 owner metadata 자동 부착 (minimal — 기존 컨테이너 회귀 회피) | | C2-2 | K3s callback 토큰 IP/scope 바인딩 부재 | source IP 추출·로깅 + body.server_ip 와 불일치 시 warning. 하드 차단은 PR-C 로 | | C2-3 | Auth token localStorage 평문 저장 | 이번 PR 범위 외 (PR-D) | | C2-4 | CSP script-src 'unsafe-inline' | 이번 PR 범위 외 (PR-D) | | C2-5 | Kubeconfig 다운로드 audit log 부재 | 매 GET 마다 audit_log.rec(action="kubeconfig_download") + source IP |
High (7건)
| ID | 항목 | 처리 | |—|—|—| | H2-1 | Network/LB GET·DELETE owner 검증 누락 | PR-A 처리 | | H2-2 | K3s background task admin conn 무한 진행 | PR-C 로 | | H2-3 | K8s 매니페스트 securityContext / NetworkPolicy 부재 | PR-E 로 | | H2-4 | :latest tag + imagePullPolicy: Always | PR-E 로 | | H2-5 | HAProxy 컨테이너 USER root | PR-E 로 | | H2-6 | CI dependency / image scan gate 없음 | PR-F 로 | | H2-7 | K3s 단일 마스터키 (4종 데이터) | PR-B 처리 — HKDF v3 sub-key 도메인 분리 |
Medium (11건)
| ID | 항목 | 처리 | |—|—|—| | M2-3 | Database/Volume/Snapshot/Backup IDOR | PR-A 처리 | | M2-4 | Manila access-rule owner 검증 누락 | PR-A 처리 (가장 위험: cross-tenant CephFS mount) | | M2-10 | Health Bearer 토큰 30일 sliding | PR-B 처리 — 7일 절대 만료 | | 그 외 | M2-1/2/5/6/7/8/9/11 | 후속 PR |
변경 파일 (8 commits)
PR-A — Backend IDOR 일괄
1. 9c79b24 fix(api/network)
backend/app/api/network/networks.py— get/delete network, update/delete subnet, FIP associate/disassociate/delete (외부·공유 네트워크 owner check 면제)backend/app/api/network/routers.py—_get_router_with_owner_check헬퍼, get/delete/interface/gateway 모두backend/app/api/network/security_groups.py—_get_sg_with_owner_check헬퍼, delete + rule create/deletebackend/tests/conftest.py—mock_connSDKget_*default stubbackend/tests/test_network_owner_check.py— 16 신규 케이스
2. 3b36082 fix(api/loadbalancer)
backend/app/api/network/loadbalancers.py—_assert_lb_owner헬퍼 + LB/listener/pool/member/HM 의 모든 sub-pathbackend/tests/test_loadbalancer_owner_check.py— 10 신규 케이스
3. 5365758 fix(api/database,storage)
backend/app/api/database/instances.py—_assert_db_instance_owner+_assert_db_backup_owner. instance get/delete/restart/enable_root_user/databases/users/backups + restore from backupbackend/app/api/storage/volumes.py—_assert_volume_owner+ transferbackend/app/api/storage/volume_snapshots.py— snapshot get/delete + create (volume owner)backend/app/api/storage/volume_backups.py— backup get/delete/restore + create (volume owner)backend/tests/test_database_owner_check.py(10) +test_volume_owner_check.py(11)
4. 3df11be fix(object-storage)
backend/app/services/swift.py—create_container에X-Container-Meta-Owner-Project-Id자동 부착 (SDK + raw PUT + SDK 재시도 3 경로 모두). 메타 부착 실패는 컨테이너 생성을 무효화하지 않음 (best-effort).backend/tests/test_swift_owner_metadata.py— 3 신규 케이스
5. 093bfd8 fix(api/file-storage)
backend/app/api/storage/file_storage.py—_assert_share_owner+_fetch_and_assert_share_owner헬퍼. delete + access-rule list/grant/revoke 의 비대칭 해소backend/tests/test_file_storage.py,test_create_access_rule_metadata.py— 기존 테스트 manila.get_file_storage patch 추가backend/tests/test_file_storage_owner_check.py— 7 신규 케이스 (public share 면제 포함)
PR-B — K3s 보안
6. 836dfde fix(k3s)
backend/app/api/k3s/clusters.py—download_kubeconfig에audit_log.rec+ source_ip 기록 (HEAD 미기록)backend/app/api/k3s/callback.py— source_ip 추출 + 토큰 소비/거부 모두 로그, body.server_ip 와 불일치 시 warningbackend/tests/test_k3s_kubeconfig_audit.py— 3 신규 케이스
7. 0be375e fix(k3s,health) — HKDF + Health TTL
backend/app/services/k3s_crypto.py—_derive_subkey(HKDF-SHA256, info=b”afterglow-k3s/"), v3 prefix 도입. v2/legacy fallback 유지 + `_warn_legacy_once` (도메인 단위 1회 warning) backend/app/services/instance_health.py—_TOKEN_TTL30일→7일,verify_report_token의 sliding TTL 갱신 제거backend/tests/test_k3s_crypto_v3_subkey.py— 6 신규 케이스 (sub-key 분리, cross-domain reject, fallback warning)backend/tests/test_k3s_crypto.py,test_keystone_appcred.py— v2 → v3 prefix assertion updatebackend/tests/test_instance_health.py— TTL=7일 + verify 가 expiry 갱신 안 함 검증
8. ca7107e chore: ruff format — 8개 파일 스타일 정리
행동 변화 (운영 영향)
Backward-compatible — 마이그레이션 불필요
- 기존 v1/v2 ciphertext 는 자동으로 복호화. deprecation warning 1회 (도메인별) 만 로그. 신규 암호화는 모두 v3 prefix.
- 기존 기능 모두 정상 동작 (cross-project 접근 시도가 없는 정상 사용자).
행동 변화 — 알아둘 것
| 변화 | 영향 | 대응 |
|---|---|---|
| 다른 프로젝트 자원 ID 로 GET/DELETE → 404 (이전: 200/403) | ops 자동화 스크립트가 admin 권한 없이 cross-project 접근하던 케이스가 있었다면 깨짐 | admin 토큰 사용 또는 application credential |
| Kubeconfig GET 마다 audit log 1줄 + DB row | 다운로드가 잦은 환경에서 audit table row 증가 | audit_log 적절한 retention policy 적용 |
| Health Bearer 토큰 7일 후 만료 | VM 부팅 후 7일 이상 health_check.sh 가 토큰을 갱신 안 하면 cephx rotate 실패 | health_check.sh 의 재발급 흐름 동작 확인 (이미 구현돼 있음) |
_warn_legacy_once 의 module-level set | gunicorn/uvicorn N workers 에서 워커당 1회 warning (총 N회) | 정상 — log 노이즈 적음 |
다음 릴리스에서 변경 예정 (1.15.0)
- v2/legacy crypto fallback 제거. 사전에 마이그레이션 스크립트로 모든 ciphertext 를 v3 로 batch re-encrypt 필요 (별도 PR 제공).
- 마이그레이션 미실행 시 v1/v2 ciphertext 가 영구 복호화 불가 가 된다.
운영 체크리스트
배포 전
- 1.13.9 에서 작성된 v1/v2 ciphertext (kubeconfig / node_token / manager_password / notion) 의 갯수 확인 (
SELECT COUNT(*) FROM cluster_record WHERE encrypted_kubeconfig NOT LIKE 'v3:%'등) - audit_log 테이블 retention policy 확인 (kubeconfig_download row 증가 대비)
- cross-project 접근하는 ops 스크립트 / dashboards 식별
배포 후
_logger에legacy ciphertext detectedwarning 이 워커당 1회씩만 뜨는지 확인 (스팸 X)- kubeconfig 다운로드 →
audit_log에kubeconfig_downloadrow 와source_ipextra 기록 확인 - Health Bearer 토큰: 새 인스턴스 생성 후 토큰 TTL = 7일 인지 확인 (
TTL afterglow:health:token:<token>)
1.15.0 (예정) 전
- 마이그레이션 스크립트로 모든 v1/v2 ciphertext → v3 re-encrypt
- dry-run 으로 영향 행 수 확인 후 실행
advisor 검토 반영 사항
이번 PR 은 advisor (강화 검토 모델) 의 세 가지 우려를 반영해 minimal 방향으로 조정했다 (사용자 승인):
- Object-storage IDOR (C2-1) — Swift 의 account 모델 (project-scoped Keystone token = 그 account 의 컨테이너만 접근) 이 1차 방어선임을 코드 검증 (
backend/app/api/deps.py:175). metadata 기반 검증을 추가하면 기존 컨테이너 (메타 없음) 가 모두 404 되는 회귀 위험이 큼 → 신규 컨테이너에 metadata 부착만, 검증 자체는 추가하지 않음. - Kubeconfig redemption URL (C2-5) — nonce-only URL 은 server log/Referer/history 로 leak 위험이 새로 생김. 현재 X-Auth-Token 인증이 더 안전 → redemption URL endpoint 신설 제외, audit log 만 추가.
- HKDF + v1/v2 fallback 동시 제거 (H2-7) — 마이그레이션 부분 실패 시 ciphertext 영구 손실. → HKDF sub-key 추가 + v2/legacy fallback 유지 + deprecation log. v1/v2 제거는 다음 PR.
향후 보안 작업 (별도 PR)
- PR-C — Background task token lifetime 검증 + cephx rotate race lock
- PR-D — Frontend localStorage 토큰 → HttpOnly cookie 전환 + CSP nonce (큰 리팩토링)
- PR-E — K8s securityContext + NetworkPolicy + digest pin + HAProxy non-root
- PR-F — CI scanning (bun audit / pip-audit / trivy) + dependabot
- PR-G — Manila CSI application credential + extend-session CSRF + WebSocket subprotocol
- PR-H — Low 항목 일괄 (SECRET_KEY 엔트로피, Grafana JWT TTL, SecretStr, .gitignore, Google Fonts CSP 등)
- v2/legacy crypto fallback 제거 + 마이그레이션 스크립트 + dry-run 모드
검증 결과
- 단위 테스트: 1247 passed, 20 skipped, 0 failed
- ruff check + format check: 통과
- 7 commit + 1 format commit (총 8) push 완료