POSA_LEAKSMS/sql/migration_drop_leak.sql
cw2007 73e49fd528 feat: RPi4 누수+온습도 2-모듈 단일 서버 통합 + 보안 하드닝
2번 모듈 신규(raspberry_pi4_sht30/): SHT30 온습도 → cafe24, raw-body X-Signature HMAC, 폐쇄망 오프라인 설치(vendor/install), systemd, CRC/포맷 자가점검. firmware sht30_fw wire 포맷과 byte-parity 검증.

1번 모듈 통합(raspberry_pi4/): 누수+카메라를 동일 서버로. 레거시 sha256(key+json) body-sig → X-Signature HMAC 전환, 사진 canonical=device_id|sensor_id|log_id|timestamp.

서버 통합(php/): api/sensor_data.php 누수 이벤트 수신, api/photo_upload.php 신규(HMAC·finfo·서버생성 파일명·nonce 멱등·sensor 결속), photo.php 인증 사진서빙, dashboard.php 누수 패널+사진, sms_send.php 누수 SMS, config.php 태그/요약.

DB: sql/migration_add_leak.sql(가산·멱등), schema_sht30.sql 통합 스키마.

보안 하드닝(외부 펜테스트 대비, 확정 CRITICAL/HIGH 0): cron_heartbeat api_key fail-closed, setup_mfa 부트스트랩 토큰 post-enrollment 잠금, var/ PHP 실행차단, HSTS.

문서/테스트: SYSTEM_OVERVIEW.md, security_hardening_test.php 갱신. 참고: 사용자 요청으로 세션 외 기존 미커밋 변경도 함께 포함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:21:11 +09:00

106 lines
5.7 KiB
SQL

-- =============================================================================
-- migration_drop_leak.sql - 기존 누수(v2605) 설치 → SHT30 온습도 전용 전환
--
-- ⚠️ 비가역(DROP COLUMN/TABLE) 변경입니다. 반드시 DB 전체 백업 후 실행하세요.
-- 운영 데이터(과거 누수 이벤트/사진/사고 이력)가 삭제됩니다.
--
-- 이미 v2605 스키마(sensor_log/sensor_status/sensor_metric/sms_log + leak_*)가
-- 적용된 운영 DB를 SHT30 전용으로 정리합니다. 측정 이력(sensor_metric)과 SMS
-- 이력(sms_log)은 보존합니다.
--
-- 신규 설치라면 이 파일이 아니라 sql/schema_sht30.sql 하나만 실행하세요.
-- =============================================================================
SET NAMES utf8mb4;
-- 0) sensor_metric 이 없던 구버전 대비: 없으면 생성(있으면 무시).
CREATE TABLE IF NOT EXISTS `sensor_metric` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`device_id` VARCHAR(64) NOT NULL DEFAULT '',
`device_location` VARCHAR(100) NOT NULL DEFAULT '',
`sensor_id` TINYINT UNSIGNED NOT NULL,
`sensor_name` VARCHAR(100) NOT NULL DEFAULT '',
`metric_type` VARCHAR(30) NOT NULL DEFAULT 'sht30',
`temperature_c` DECIMAL(6,2) NULL,
`humidity_percent` DECIMAL(5,2) NULL,
`metric_status` VARCHAR(30) NOT NULL DEFAULT 'normal',
`measured_at` DATETIME NOT NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_sensor_measured` (`sensor_id`, `measured_at`),
INDEX `idx_device_sensor` (`device_id`, `sensor_id`),
INDEX `idx_metric_type` (`metric_type`, `created_at`),
INDEX `idx_status_measured` (`metric_status`, `measured_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 1) 누수 전용 테이블 제거
DROP TABLE IF EXISTS `leak_photo`;
DROP TABLE IF EXISTS `leak_incident`;
-- 2) sensor_log: 누수 enum 값 정리 → event_type 재정의 (둘 다 멱등)
-- (leak_alert/leak_recovery 행이 있으면 periodic 으로 강등 후 enum 축소.
-- 이미 정리된 DB면 매칭 행이 없어 no-op, MODIFY 도 동일 enum 재적용이라 안전.)
UPDATE `sensor_log` SET `event_type` = 'periodic'
WHERE `event_type` IN ('leak_alert', 'leak_recovery');
ALTER TABLE `sensor_log`
MODIFY `event_type` ENUM('startup','periodic') NOT NULL DEFAULT 'periodic';
-- 3~4) 인덱스/컬럼 제거·추가는 "존재할 때만" 수행하여 재실행해도 오류가 없게 한다.
-- (MySQL/MariaDB 는 DROP COLUMN/INDEX 에 IF EXISTS 호환이 일정치 않으므로
-- information_schema 로 가드하는 멱등 프로시저를 사용한다.)
DROP PROCEDURE IF EXISTS _mig_drop_leak;
DELIMITER //
CREATE PROCEDURE _mig_drop_leak()
BEGIN
DECLARE db VARCHAR(64);
SELECT DATABASE() INTO db;
-- sensor_log: is_leak 인덱스/컬럼 제거, event_type 인덱스 추가
IF EXISTS (SELECT 1 FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = db AND TABLE_NAME = 'sensor_log' AND INDEX_NAME = 'idx_is_leak') THEN
ALTER TABLE `sensor_log` DROP INDEX `idx_is_leak`;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = db AND TABLE_NAME = 'sensor_log' AND COLUMN_NAME = 'is_leak') THEN
ALTER TABLE `sensor_log` DROP COLUMN `is_leak`;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = db AND TABLE_NAME = 'sensor_log' AND INDEX_NAME = 'idx_event_type') THEN
ALTER TABLE `sensor_log` ADD INDEX `idx_event_type` (`event_type`, `created_at`);
END IF;
-- sensor_status: 누수 컬럼/인덱스 제거 (offline_alerted 는 오프라인 SMS에 필요 → 유지)
IF EXISTS (SELECT 1 FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = db AND TABLE_NAME = 'sensor_status' AND INDEX_NAME = 'idx_is_leak') THEN
ALTER TABLE `sensor_status` DROP INDEX `idx_is_leak`;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = db AND TABLE_NAME = 'sensor_status' AND COLUMN_NAME = 'is_leak') THEN
ALTER TABLE `sensor_status` DROP COLUMN `is_leak`;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = db AND TABLE_NAME = 'sensor_status' AND COLUMN_NAME = 'leak_periodic_count') THEN
ALTER TABLE `sensor_status` DROP COLUMN `leak_periodic_count`;
END IF;
-- sensor_metric: 임계 경보 조회용 인덱스 추가(없을 때만)
IF NOT EXISTS (SELECT 1 FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = db AND TABLE_NAME = 'sensor_metric' AND INDEX_NAME = 'idx_status_measured') THEN
ALTER TABLE `sensor_metric` ADD INDEX `idx_status_measured` (`metric_status`, `measured_at`);
END IF;
END //
DELIMITER ;
CALL _mig_drop_leak();
DROP PROCEDURE IF EXISTS _mig_drop_leak;
-- 5) 수집 재전송(replay) 멱등 nonce 테이블 추가(없을 때만 — 가산/멱등).
-- api/sensor_data.php 가 서명을 nonce 로 기록해 시계오차 창 내 중복 수신을 멱등
-- 처리한다(중복 저장·중복 SMS 방지). 이 테이블이 없어도 수집은 동작한다.
CREATE TABLE IF NOT EXISTS `ingest_nonce` (
`signature` VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
`seen_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`signature`),
INDEX `idx_seen_at` (`seen_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='수집 재전송 멱등 nonce (시계오차 창 내 중복 차단; 주기적 정리)';