fix(installer): 설치 상태 파일에 관리자 비밀번호가 평문으로 잔존하는 문제
비밀을 state.json(0664, 실패 시 무기한 잔존)에 기록하지 않고 runtime.php(0600, finalize 시 삭제)로 채널을 일원화한다. 정상 완료 경로에서는 finalize 가 state.json 을 삭제하지만, finalize 미호출 / unlink 실패 / 설치 중단 시에는 평문이 영구히 남았고, finalize 가드가 재호출을 410 차단해 자가 회복도 불가능했다. 삭제라는 단일 경로에 의존해 평문을 보호하던 구조 자체를 제거한다. SSE 워커는 세션에 접근할 수 없어 파일 경유가 불가피한데, 그 파일이 0664 의 state.json 이었던 것이 구조적 원인이다. 세션을 보유한 유일한 공통 시작점인 install-process.php 를 비밀 이송 지점으로 삼고, db_seed 가 소비한 즉시 제거한다. 레거시 / 실패 경로는 완료·중단·초기화·finalize 시점 redact 로 방어. 동반 수정: - 세션 config 재병합으로 무력화되던 기존 DB 비밀번호 마스킹 복구 - db_seed 후 putenv 미해제로 모든 exec 자식에 평문이 ENV 상속되던 누출 차단 - runtime read 비밀번호 폴백이 write 값으로 잘못 대체되던 문제 ( 인접) 이미 설치된 사이트는 7.0.4 업그레이드 스텝이 잔존 파일을 정리한다. 완료 증거가 있으면 삭제하고, 설치 진행 중일 수 있으면 비밀만 제거한다.
This commit is contained in:
1 parent
ae447c9cc4
commit
9be8faaea2
16 files changed
+1534
-119
No files matched your search
@@ -11,17 +11,16 @@
|
||||
* 실패 시: runtime.php 보존 → InstallerRuntimeServiceProvider 가 계속 동작
|
||||
* → 앱은 정상 (관리자 재호출 또는 다음 부팅 시 재시도 경로 확보).
|
||||
*
|
||||
* @package G7\Installer
|
||||
* @see https://github.com/gnuboard/g7/issues/23
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../includes/config.php';
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/installer-runtime.php';
|
||||
require_once __DIR__ . '/../includes/installer-state.php';
|
||||
require_once __DIR__ . '/_guard.php';
|
||||
require_once __DIR__.'/../includes/config.php';
|
||||
require_once __DIR__.'/../includes/functions.php';
|
||||
require_once __DIR__.'/../includes/installer-runtime.php';
|
||||
require_once __DIR__.'/../includes/installer-state.php';
|
||||
require_once __DIR__.'/_guard.php';
|
||||
// finalize 전용 가드 — `.env` 의 INSTALLER_COMPLETED=true 단독으로만 차단한다.
|
||||
// 일반 인스톨러 엔드포인트의 `installer_guard_or_410()` 은 `g7_installed` 락 파일도
|
||||
// 차단 사유로 삼는데, 그 락 파일은 finalize 호출 직전 단계의 complete_flag task 가
|
||||
@@ -41,7 +40,7 @@ ignore_user_abort(true);
|
||||
$accepted = json_encode(['accepted' => true]);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Content-Length: ' . strlen($accepted));
|
||||
header('Content-Length: '.strlen($accepted));
|
||||
header('Connection: close');
|
||||
|
||||
echo $accepted;
|
||||
@@ -68,11 +67,12 @@ try {
|
||||
return;
|
||||
}
|
||||
|
||||
$envPath = BASE_PATH . '/.env';
|
||||
$envPath = BASE_PATH.'/.env';
|
||||
$envBase = generateEnvContent();
|
||||
|
||||
if ($envBase === null) {
|
||||
addLog('[finalize-env] generateEnvContent() returned null — .env.example missing');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ try {
|
||||
$envPath,
|
||||
$lastError['message'] ?? 'unknown',
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -134,8 +135,14 @@ try {
|
||||
|
||||
// state.json 삭제 — setInstallationCompleteSSE 가 본 단계로 위임함
|
||||
// (finalize 가 generateEnvContent() 호출 시 state.config 가 필요했기 때문)
|
||||
//
|
||||
// 삭제 실패 또는 DELETE_INSTALLER_AFTER_COMPLETE=false 로 파일이 남는 모든 경로에서는
|
||||
// 비밀 필드를 제거한 뒤 재저장한다 (이슈 #465). .env 머지가 이미 성공한 뒤이므로
|
||||
// state.config 가 더 이상 필요하지 않아 순서상 안전하다.
|
||||
$stateFilePath = BASE_PATH.'/storage/installer-state.json';
|
||||
$stateDeleted = false;
|
||||
|
||||
if (defined('DELETE_INSTALLER_AFTER_COMPLETE') && DELETE_INSTALLER_AFTER_COMPLETE) {
|
||||
$stateFilePath = BASE_PATH . '/storage/installer-state.json';
|
||||
if (is_file($stateFilePath)) {
|
||||
if (@unlink($stateFilePath) === false) {
|
||||
addLog(sprintf(
|
||||
@@ -144,11 +151,22 @@ try {
|
||||
(error_get_last()['message'] ?? 'unknown'),
|
||||
));
|
||||
} else {
|
||||
$stateDeleted = true;
|
||||
addLog('[finalize-env] state.json 삭제 완료');
|
||||
}
|
||||
} else {
|
||||
$stateDeleted = true;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
if (! $stateDeleted && is_file($stateFilePath)) {
|
||||
if (saveInstallationState(redactInstallationStateSecrets(getInstallationState()))) {
|
||||
addLog('[finalize-env] state.json 잔존 — 비밀 필드 redact 완료');
|
||||
} else {
|
||||
addLog('[finalize-env] state.json redact 재저장 FAILED — 수동 삭제 권장: '.$stateFilePath);
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// 예외 시 runtime.php 보존 → Provider 가 계속 config 주입 → 앱 정상 동작.
|
||||
addLog('[finalize-env] unexpected exception: ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
|
||||
addLog('[finalize-env] unexpected exception: '.$e->getMessage().' @ '.$e->getFile().':'.$e->getLine());
|
||||
}
|
||||
Reference in new issue
Block a user