SHT30 온습도 모니터링 시스템 전체 소스(서버 PHP, STM32 펌웨어, SQL, 테스트). 전체 코드리뷰에서 도출된 보안 하드닝 10건 반영: - 요청 서명 HMAC-SHA256 전환(펌웨어 sig.c/서버 config.php/호스트 패리티 동시) - 재전송 방어 + 기본 API_KEY fail-closed + 디바이스 문자열 정제(api/sensor_data.php) - 오프라인 SMS 중복 발송 경합 제거(cron_heartbeat.php, 원자적 선점) - CSV 수식 주입 방지(monthly_report.php), 감사로그 회전 락(retention_cleanup.php) - 브루트포스 카운터 원자화(login.php), 예시 TOTP 비밀키 무효화, 마이그레이션 멱등화 _backup/(하드코딩 실 비밀값 포함)·config.local.php·런타임 상태는 .gitignore 제외.
91 lines
8.2 KiB
Markdown
91 lines
8.2 KiB
Markdown
# 변경 이력
|
|
|
|
## Unreleased - 보안 하드닝 (전체 코드리뷰 반영)
|
|
|
|
### ⚠️ 호환성 주의 (BREAKING)
|
|
- **요청 서명을 키 접두 SHA256 → HMAC-SHA256 으로 전환했습니다.** 길이확장(length-
|
|
extension) 공격에 견디기 위함입니다. `X-Signature = HMAC-SHA256(key=API_KEY, msg=raw_body)`.
|
|
서버(`verify_signature_raw`)·펌웨어(`sig.c`)·호스트 레퍼런스(`reference.py`)를 동시에
|
|
바꿨으므로, **펌웨어 재플래시와 서버 배포를 함께** 진행해야 합니다(구 펌웨어 ↔ 신 서버는
|
|
서명 불일치로 거부됨). 호스트 패리티 테스트와 ARM `-Wconversion -Werror` 컴파일로 검증했습니다.
|
|
|
|
### 서버 (PHP)
|
|
- `api/sensor_data.php`: 재전송(replay) 방어를 추가했습니다 — 본문 `timestamp` 가
|
|
`INGEST_MAX_CLOCK_SKEW_SEC`(기본 600초) 범위를 벗어나거나 0 이하이면 거부합니다.
|
|
- `api/sensor_data.php`: 운영 비밀값 강제(fail-closed). `API_KEY` 가 예시 기본값이면
|
|
수신을 503 으로 거부합니다(`api_key_is_default()`).
|
|
- `api/sensor_data.php`: 디바이스 문자열(device_id/sensor_name/device_location/metric_type)
|
|
의 제어문자를 제거해 SMS/로그 라인 주입을 막습니다.
|
|
- `cron_heartbeat.php`: 오프라인 SMS 중복 발송 경합을 제거했습니다 — `offline_alerted`
|
|
0→1 전이를 원자적 조건부 UPDATE 로 선점한 프로세스만 발송합니다.
|
|
- `monthly_report.php`: CSV 내보내기의 수식 주입(formula injection)을 방지합니다(자유
|
|
텍스트 셀이 `= + - @` 등으로 시작하면 무력화).
|
|
- `retention_cleanup.php`: 감사로그 회전을 배타 락(flock)으로 묶어 동시 기록 유실을 막습니다.
|
|
- `login.php`: 브루트포스 카운터를 원자적 읽기-수정-쓰기로 변경해 동시 실패 시 카운트
|
|
덮어쓰기(lost update)를 막습니다.
|
|
- `config.local.example.php`: 예시 TOTP 비밀키를 공개 테스트 값에서 명백히 무효한
|
|
플레이스홀더로 교체했습니다(MFA 가 실제 비밀키 설정 전까지 fail-closed).
|
|
|
|
### DB
|
|
- `sql/migration_drop_leak.sql`: information_schema 가드 프로시저로 멱등화했습니다(재실행/
|
|
부분 적용 DB 에서도 오류 없이 진행).
|
|
|
|
## Unreleased - SHT30 온습도 전용 전환
|
|
|
|
### 시스템 범위
|
|
- 시스템을 서버실 **온습도(SHT30) 24시간 모니터링 + 임계 초과 SMS + 웹 대시보드** 전용으로 전환했습니다. 누수 감지/카메라/사진/사고대응(incident) 기능은 전면 제거했습니다.
|
|
- 단일 보드 구성으로 단순화: `sht30_fw`(`sensor_id=2`, `device_id=stm32-sht30-01`)만 사용합니다. 누수 보드(`board_leak`/`leak_fw`)는 제거했습니다.
|
|
|
|
### 서버 (PHP/DB)
|
|
- 온습도 임계 SMS를 추가했습니다: 고온 30℃ / 저온 10℃ / 고습 70% / 저습 20%, 복귀 히스테리시스 ±1℃·±3%, 동일 종류 쿨다운 1800초(30분). 임계는 서버 판정값(`php/config.php` `METRIC_*`, `config.local.php` override)이라 폐쇄망에서 펌웨어 재플래시 없이 변경 가능합니다.
|
|
- 측정값을 `sensor_metric`에 저장하고 `metric_status`로 경보/정상복귀를 판정합니다. 장비 오프라인/복구는 `HEARTBEAT_TIMEOUT_SEC`(기본 1200초=20분)으로 판정합니다.
|
|
- 사진 업로드/열람 경로(`api/photo_upload.php`, `photo.php`)와 사고 대응(`incidents.php`)을 제거했습니다.
|
|
- 신규 설치용 통합 스키마를 `sql/schema_sht30.sql`로 정리했습니다(테이블: `sensor_log`, `sensor_status`, `sensor_metric`, `sms_log`). 기존 누수 설치 전환용 `sql/migration_drop_leak.sql`을 추가해 레거시 누수 컬럼/테이블(`leak_photo`, `leak_incident`, `is_leak` 등)을 정리합니다.
|
|
|
|
### 펌웨어 (STM32F407 `sht30_fw`)
|
|
- 누수 이벤트 본문에서 `is_leak` 와이어 필드를 제거하고 온습도 측정 보고로 단일화했습니다.
|
|
- SHT30 I2C 배선을 확정했습니다: I2C1 PB6(SCL)/PB7(SDA), 주소 0x44, 3.3V/GND, 4.7kΩ 풀업. 측정 주기 `APP_SHT30_REPORT_INTERVAL_SEC=300`(5분).
|
|
- 레거시 4.7MΩ 누수 풀다운 회로 및 e-con 5C-08 결선 안내를 폐기했습니다.
|
|
|
|
### 문서
|
|
- README, 설치/운영 문서, 배선도, 정리 보고서를 SHT30 온습도 전용으로 재작성했습니다. 누수/사진/카메라/사고대응 관련 절·표를 온습도 임계 SMS/쿨다운/오프라인 기준으로 치환했습니다.
|
|
|
|
## Unreleased - 메인 MCU 마이그레이션 (Raspberry Pi → STM32F407VGT6)
|
|
|
|
### 서버 (PHP)
|
|
- `sensor_data.php` 인증을 이중 모드로 확장했습니다: `X-Signature` 헤더 기반 raw-body 서명(신규, STM32 펌웨어용)과 기존 본문 필드 서명(RPi, 하위 호환)을 모두 수용합니다.
|
|
- `config.php`에 `verify_signature_raw()`를 추가했습니다 — `sha256(API_KEY . 요청본문)`로 검증하여 JSON 재직렬화/부동소수 포맷 차이로 인한 서명 불일치(R1)를 제거합니다.
|
|
- raw-body/레거시 서명 회귀 테스트 `tests/raw_body_signature_test.php`를 추가했습니다.
|
|
|
|
### 펌웨어 (신규 `firmware/`, STM32F407VGT6 / Cortex-M4F)
|
|
- 메인 MCU를 Raspberry Pi Zero 2 W(Linux/Python)에서 STM32F407VGT6 베어메탈로 이식했습니다. 구성: 유선 Ethernet(LAN8720 RMII) + LwIP + mbedTLS(HTTPS) + FreeRTOS. WiFi/카메라는 제거.
|
|
- 두 보드 변형: `BOARD_LEAK`(누수, sensor_id=1), `BOARD_SHT30`(온습도, sensor_id=2). RPi 2대 구성을 미러링.
|
|
- 이식 가능한 코어(`sha256_sw`/`sig`/`jsonbody`/`sht30_convert`/`httpapi`)는 호스트 패리티(Python↔PHP `verify_signature_raw`) + ARM 크로스컴파일(`-Werror -Wconversion`)로 검증했습니다.
|
|
- 다중 에이전트 코드리뷰에서 확인된 결함을 수정: TLS 인증서 유효기간 검증 활성화(RTC/SNTP 시간 공급), FreeRTOS 정적메모리 콜백 중복정의 제거, LwIP 코어락 위반(PHY 링크/DNS) 수정, 로그 UART PA2 충돌 → USART3(PD8/PD9) 이동, EXTI 핸들러 추가, HW RNG 기반 `bsp_rand32`.
|
|
- 폐쇄망 빌드: 의존성은 `firmware/third_party`에 벤더링, 오프라인 빌드/플래시 절차 문서화(`firmware/docs/BUILD_OFFLINE.md`).
|
|
- `docs/stm32f407_migration_plan.md`(계획+구현 현황), `firmware/docs/{HARDWARE,PORTING_NOTES}.md`를 추가했습니다.
|
|
- 레거시 RPi 펌웨어(`raspberry_pi/`)와 systemd 유닛(`systemd/`)을 제거했습니다(STM32 펌웨어로 대체).
|
|
|
|
## v2605 - 2026-05-19
|
|
|
|
- v2605 정리 전 원본을 `_backup/pre_v2605_20260519/`에 보관했습니다.
|
|
- PHP/RPi 운영 비밀값을 코드 하드코딩에서 로컬 설정/환경 변수 방식으로 분리했습니다.
|
|
- `config.local.example.php`, `config.example.py`, `leak-sensor.env.example`을 추가했습니다.
|
|
- 누수경보 SMS 쿨다운이 실제 발송 로직에 적용되도록 수정했습니다.
|
|
- 누수 지속 SMS를 시간 기반 5분 정책으로 정리했습니다.
|
|
- heartbeat 체크 주기와 오프라인 판정 시간을 분리했습니다.
|
|
- 사진 업로드가 실제 `leak_alert` 로그 ID를 검증하도록 강화했습니다.
|
|
- 신규 설치용 통합 DB 스키마 `sql/schema_v2605.sql`을 추가했습니다.
|
|
- README와 보고서를 실제 코드 기준으로 최신화했습니다.
|
|
- 누수 사고 대응 테이블과 대시보드 사고 패널을 추가했습니다.
|
|
- 누수 사고 `확인함`, `출동 중`, `조치 완료`, `오탐` 상태 변경을 지원합니다.
|
|
- 미확인 누수 사고 SMS 에스컬레이션을 추가했습니다.
|
|
- 대시보드 첫 화면을 위험 상태 우선 구조로 재배치했습니다.
|
|
- 운영 자가진단 패널을 추가해 설정값, 마이그레이션, 권한, SMS 실패를 표시합니다.
|
|
- 30일 운영 요약을 추가해 사고 상태, 평균 확인/조치 시간, SMS 실패, 사진 저장 현황을 보여줍니다.
|
|
- 설치/운영 점검 화면 `php/setup_wizard.php`를 추가했습니다.
|
|
- 보안대책서 첨부용 증적 보고서 `php/security_evidence.php`와 운영·보안 체크리스트를 추가했습니다.
|
|
- 월간 보고서 화면 `php/monthly_report.php`를 추가했습니다.
|
|
- 누수 사진 타임라인 다중 촬영을 추가했습니다.
|
|
- 사진 타임라인 DB 컬럼과 기존 설치용 `sql/migration_photo_timeline.sql`을 추가했습니다.
|
|
- Pi 증적 수집 스크립트를 추가해 API 키 원문 없이 서비스/포트/환경파일 상태를 확인할 수 있게 했습니다.
|