Files
Gnuboard7/tests/scenarios/database-credential-guard.yaml
HeuJung 062a88ca04 feat(core): DB 최고권한 계정 차단 + OPcache 상태 안내
설치 단계와 코어 부팅의 정책이 엇갈려 있었다. 인스톨러는 root 를 자유롭게
허용하면서, 설치 후 부팅은 같은 값을 보고 확장 로딩 전체를 조용히 스킵했다.
설치는 성공하지만 이후 깨진 화면만 뜨고 원인은 로그 한 줄에만 남았다.

판정을 프레임워크 의존성 0 인 공용 SSoT 로 모아 인스톨러와 코어가 같은 답을
내게 하고, 설치 단계에서 서버 2경로로 차단하며, 설치 후 변경은 전용 에러
페이지로 드러낸다. 가드를 boot 가 아닌 미들웨어에 두어 artisan 이 죽지 않게
해 복구 수단을 남겼다. admin 은 일반 권한 운영 계정 오차단을 막기 위해 차단
목록에서 제외했다.

OPcache 는 상태 조회 경로 자체가 없어 성능 저하를 인지할 방법이 없었다.
설치 단계 경고(비차단)와 관리자 정보 탭 표시를 같은 판정 SSoT 로 추가했다.
확장 로드 여부만으로는 부족해 opcache.enable 지시자까지 확인하며, ini_get 이
차단된 환경은 "확인 불가"로 폴백해 경고도 차단도 하지 않는다.
2026-07-20 14:17:49 +09:00

82 lines
5.3 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
feature: DB 최고권한 계정 차단 및 설치 후 자격증명 오류 안내
description: |
G7 은 DB 사용자명을 두 계층에서 다르게 다뤄 왔다. 인스톨러는 `root` 를 자유롭게
허용했고(오히려 `.env.example` fallback 기본값이 DB_WRITE_USERNAME=root), 설치 후
코어 부팅(CoreServiceProvider::boot)은 같은 값을 보고 모듈·플러그인·템플릿 로딩
전체를 조용히 스킵했다. 결과적으로 설치는 성공하지만 이후 모든 요청에서 템플릿 없는
깨진 화면이 뜨고, 원인은 storage/logs 의 ERROR 한 줄에만 남아 운영자가 알 방법이 없었다.
본 기능은 두 계층의 정책을 하나로 맞춘다.
- 판정 SSoT: App\Support\PrivilegedDatabaseAccounts (프레임워크 의존성 0 —
인스톨러가 require_once 로 직접 로드해 코어와 같은 목록·같은 비교 규칙을 쓴다)
- 설치 단계: 서버 2경로(연결 테스트 API, Step 3 폼 POST) + 클라이언트 1경로로 차단
- 설치 후: DatabaseCredentialGuard 미들웨어가 전용 에러 페이지(503)로 원인을 드러냄
`admin` 은 의도적으로 차단 목록에서 제외한다 — 일반 권한으로 운영 중인 `admin`
계정 사용자가 업그레이드 직후 에러 페이지에 갇히는 오차단을 막기 위함이다.
가드를 ServiceProvider::boot 가 아니라 미들웨어에 두는 이유: boot 에서 abort 하면
`php artisan` 자체가 죽어 복구 수단(config:clear 등)을 잃는다. 미들웨어는 웹 요청만
타므로 콘솔이 자연히 통과한다.
axes:
account_kind: [normal, privileged, empty]
enforcement_point: [judgement_ssot, installer_step3_write, installer_step3_read, runtime_web, runtime_api]
install_state: [completed, not_completed]
cross_product: |
account_kind × enforcement_point 이 핵심이다. 서버 경로 중 하나만 막으면 다른 경로로
우회되고, Write 만 막고 Read 를 열어두면 Read DB 로 최고권한 계정이 들어온다.
대문자(`ROOT`)·앞뒤 공백(` root `) 같은 표기 변형은 별도 axis 로 두지 않는다.
판정 SSoT 한 곳에서 정규화하므로 모든 enforcement_point 가 같은 결과를 공유하며,
변형 자체는 DataProvider 로 전수 검증하고 effect
`privileged_account_detected_case_and_whitespace_insensitive` 로 대표한다.
같은 이유로 `postgres`/`sa`/`sys`/`system` 도 privileged 하나로 묶는다.
exclusions:
- { enforcement_point: judgement_ssot, install_state: not_completed, reason: "순수 판정 클래스 — 설치 상태와 무관 (config 접근 없음)" }
- { enforcement_point: installer_step3_write, install_state: completed, reason: "인스톨러는 정의상 미설치 상태 전용 — 설치 완료 후에는 _guard.php 가 410 으로 차단" }
- { enforcement_point: installer_step3_read, install_state: completed, reason: "동일 — 인스톨러 경로는 미설치 전용" }
- { enforcement_point: runtime_api, install_state: not_completed, reason: "미설치 early-return 은 API/HTML 분기보다 앞에서 일어나 분기에 도달하지 않음 — runtime_web 이 대표" }
- { account_kind: normal, enforcement_point: runtime_web, install_state: not_completed, reason: "정상 계정 + 미설치는 통과 사유가 둘 다 성립 — 어느 가드가 통과시켰는지 구별 불가하여 검증가치 없음. privileged/empty 로 미설치 통과를 검증" }
effects:
# 판정 SSoT
- privileged_account_detected_case_and_whitespace_insensitive
- admin_account_not_blocked
- empty_username_rejected_as_unusable
- normal_username_accepted
- installer_and_core_share_the_same_judgement_class
# 설치 단계
- installer_step3_post_rejects_privileged_username
- installer_step3_post_rejects_empty_username
- installer_step3_post_accepts_normal_username
- installer_message_substitutes_username_and_offers_alternative
- empty_and_privileged_produce_distinct_messages
# 설치 후 런타임
- runtime_returns_503_error_page
- runtime_returns_503_json_for_api
- runtime_passes_through_with_normal_account
- runtime_passes_through_when_not_installed
- error_page_does_not_leak_account_name
- error_page_is_self_contained_without_external_assets
- error_page_detects_locale_without_setlocale_middleware
- maintenance_mode_takes_precedence_over_credential_guard
# E2E axis 없음: 인스톨러는 설치 전 전용 순수 PHP 화면이라 Playwright 인증 픽스처
# (issueToken/authenticatePage) 가 성립하지 않고, 런타임 가드는 .env 를 최고권한
# 계정으로 바꿔야 재현되어 E2E 실행 환경 자체를 파괴한다. 두 경로 모두 Feature/Unit 으로
# 검증하고, 브라우저 실측은 Chrome MCP 수동 매트릭스로 대체한다.
# (레이아웃 JSON / 컴포넌트 TSX / 엔진 TS 변경 없음 — installer.js 는 인스톨러 전용
# 독립 스크립트로 템플릿 엔진 자산이 아니다.)
#
# 클라이언트 JS 검증(installer.js)도 axis 로 두지 않는다: 즉시 피드백 용도이며 차단의
# 실체는 서버 2경로다. 목록도 서버 상수를 그대로 내려받아 쓰므로 독립 판정 지점이 아니다.
test_files:
- tests/Unit/Support/PrivilegedDatabaseAccountsTest.php
- tests/Unit/Installer/PrivilegedDbAccountGuardTest.php
- tests/Feature/Middleware/DatabaseCredentialGuardTest.php