설치 단계와 코어 부팅의 정책이 엇갈려 있었다. 인스톨러는 root 를 자유롭게 허용하면서, 설치 후 부팅은 같은 값을 보고 확장 로딩 전체를 조용히 스킵했다. 설치는 성공하지만 이후 깨진 화면만 뜨고 원인은 로그 한 줄에만 남았다. 판정을 프레임워크 의존성 0 인 공용 SSoT 로 모아 인스톨러와 코어가 같은 답을 내게 하고, 설치 단계에서 서버 2경로로 차단하며, 설치 후 변경은 전용 에러 페이지로 드러낸다. 가드를 boot 가 아닌 미들웨어에 두어 artisan 이 죽지 않게 해 복구 수단을 남겼다. admin 은 일반 권한 운영 계정 오차단을 막기 위해 차단 목록에서 제외했다. OPcache 는 상태 조회 경로 자체가 없어 성능 저하를 인지할 방법이 없었다. 설치 단계 경고(비차단)와 관리자 정보 탭 표시를 같은 판정 SSoT 로 추가했다. 확장 로드 여부만으로는 부족해 opcache.enable 지시자까지 확인하며, ini_get 이 차단된 환경은 "확인 불가"로 폴백해 경고도 차단도 하지 않는다.
82 lines
5.3 KiB
YAML
82 lines
5.3 KiB
YAML
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
|