Compare commits
2 commits
73e49fd528
...
1289230f36
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1289230f36 | ||
|
|
61d19501fd |
2 changed files with 185 additions and 0 deletions
36
.claude/settings.local.json
Normal file
36
.claude/settings.local.json
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(tail:*)",
|
||||||
|
"Bash(cd \"C:/Users/Posafe/leak_sms_v2/windows_checker\" && pyinstaller --onefile --noconsole --name sensor_checker sensor_checker.py 2>&1)",
|
||||||
|
"Bash(cd \"C:/Users/Posafe/leak_sms_v2/windows_checker\" && python sensor_checker.py 2>&1)",
|
||||||
|
"Bash(sort -k2)"
|
||||||
|
],
|
||||||
|
"deny": [
|
||||||
|
"Bash(rm -rf:*)",
|
||||||
|
"Bash(rm -fr:*)",
|
||||||
|
"Bash(sudo rm:*)",
|
||||||
|
"Bash(chmod -R 777:*)",
|
||||||
|
"Bash(chmod 777:*)",
|
||||||
|
"Bash(dd:*)",
|
||||||
|
"Bash(mkfs:*)",
|
||||||
|
"Bash(:(){ :|:& };:)",
|
||||||
|
"Bash(git push --force:*)",
|
||||||
|
"Bash(git push -f:*)",
|
||||||
|
"Bash(curl:* | sh)",
|
||||||
|
"Bash(curl:* | bash)",
|
||||||
|
"Bash(wget:* | sh)",
|
||||||
|
"Bash(wget:* | bash)",
|
||||||
|
"Bash(ssh:*)",
|
||||||
|
"Bash(scp:*)",
|
||||||
|
"Read(./**/config.local.php)",
|
||||||
|
"Read(./**/config_local.py)",
|
||||||
|
"Read(./**/secrets.h)",
|
||||||
|
"Read(./**/.env)",
|
||||||
|
"Read(./**/.env.*)",
|
||||||
|
"Read(./**/*.pem)",
|
||||||
|
"Read(./**/id_rsa)",
|
||||||
|
"Read(./**/id_ed25519)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
149
PR_BODY.md
Normal file
149
PR_BODY.md
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
# RPi4 누수+온습도 2-모듈 단일 서버 통합 + 보안 하드닝
|
||||||
|
|
||||||
|
## 1. 배경 / 목적
|
||||||
|
|
||||||
|
서버실 모니터링을 **하나의 시스템**으로 통합한다. Raspberry Pi 4 센서 모듈 2개가 각각
|
||||||
|
cafe24 서버로 데이터를 전송하고, 서버가 저장·임계판정·SMS·**단일 대시보드 표시**를 담당한다.
|
||||||
|
|
||||||
|
- **1번 모듈 — 누수 + 카메라** (`raspberry_pi4/`, `sensor_id=1`)
|
||||||
|
- **2번 모듈 — 온습도 SHT30** (`raspberry_pi4_sht30/`, `sensor_id=2`)
|
||||||
|
|
||||||
|
기존에는 온습도(이 repo의 `php/` 서버)와 누수(레거시 누수 서버)가 **분리**되어 있었고
|
||||||
|
서명 방식도 달랐다. 본 PR로 **두 모듈을 동일 서버·동일 서명(X-Signature HMAC)으로 통합**하여
|
||||||
|
대시보드 한 화면에서 **온습도 + 누수 상태 + 현장 사진**을 함께 본다.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────┐
|
||||||
|
│ 1번 모듈 (누수+카메라) │ leak_alert/recovery + 사진
|
||||||
|
│ raspberry_pi4/ │──┐
|
||||||
|
└──────────────────────────┘ │ HTTPS POST
|
||||||
|
▼ X-Signature = HMAC-SHA256(API_KEY, body)
|
||||||
|
┌──────────────────────────┐ ┌─────────────────────────────┐
|
||||||
|
│ 2번 모듈 (온습도 SHT30) │ │ cafe24 서버 (php/) │
|
||||||
|
│ raspberry_pi4_sht30/ │─►│ api/sensor_data.php (수신) │
|
||||||
|
└──────────────────────────┘ │ api/photo_upload.php (사진) │
|
||||||
|
│ → MySQL / 임계·누수 SMS │
|
||||||
|
│ → dashboard.php (통합 대시보드)│
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 2번 모듈 신규 — `raspberry_pi4_sht30/` (온습도)
|
||||||
|
|
||||||
|
STM32 펌웨어 `sht30_fw` 동작을 **Raspberry Pi 4 + Python**으로 이식.
|
||||||
|
|
||||||
|
| 파일 | 역할 |
|
||||||
|
|---|---|
|
||||||
|
| `sht30_sensor.py` | 메인 — 5분 주기 측정 → 서명 POST → `startup`→`periodic`, SIGTERM 정상종료 |
|
||||||
|
| `sht30_driver.py` | SHT30 I2C 측정 + CRC-8 검증 + 변환 (smbus2, 미가용 시 시뮬레이션 폴백) |
|
||||||
|
| `wire.py` | 본문 JSON + raw-body HMAC 서명 (펌웨어 `jsonbody.c`/`sig.c`와 **바이트 동일**) |
|
||||||
|
| `config.py` / `config_local.example.py` | 비밀 아닌 기본값 / 운영·비밀값 템플릿(fail-closed) |
|
||||||
|
| `install.sh` / `vendor.sh` | 폐쇄망 오프라인 + 온라인 설치 / 의존성 사전 다운로드, I2C 자동 활성화 |
|
||||||
|
| `test_sensor.py` / `test_connection.py` | CRC·포맷 자가점검 / 서버 서명 POST 점검 |
|
||||||
|
| `systemd/sht30-sensor.service` | 부팅 자동 실행 |
|
||||||
|
|
||||||
|
- **I2C 배선:** SDA=GPIO2(물리3), SCL=GPIO3(물리5), 3.3V/GND, ADDR=GND→`0x44`, 4.7kΩ 풀업
|
||||||
|
- **측정식:** `temp=-45+175·raw_t/65535`, `rh=100·raw_rh/65535`, CRC-8(poly 0x31, init 0xFF)
|
||||||
|
- **보고 필드 순서(서명 호환 고정):** `device_id, device_location, sensor_id, sensor_name,
|
||||||
|
event_type, timestamp, metric_type="sht30", temperature_c, humidity_percent, metric_status, app_version`
|
||||||
|
- 임계 판정(고온/저온/고습/저습)·SMS는 **전부 서버**가 수행. 모듈은 측정만 보고.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 1번 모듈 통합 — `raspberry_pi4/` (누수+카메라)
|
||||||
|
|
||||||
|
- **서명 전환:** 레거시 `sha256(API_KEY + 정렬 JSON)` body-field 서명 → **raw-body
|
||||||
|
`X-Signature` HMAC**(2번 모듈과 동일 스킴). 본문을 고정 순서 문자열로 만들어 그 바이트에
|
||||||
|
서명하고 그대로 POST(`data=body_bytes`).
|
||||||
|
- **누수 이벤트 본문:** `{device_id, device_location, sensor_id, sensor_name, event_type,
|
||||||
|
is_leak, timestamp}`
|
||||||
|
- **사진 업로드 서명:** canonical = `device_id|sensor_id|log_id|timestamp` (공백 없음),
|
||||||
|
`X-Signature` 헤더. 서버 `photo_upload.php` 재구성 canonical과 바이트 동일.
|
||||||
|
- GPIO 인터럽트 누수감지 / 카메라 촬영 / 재시도 / 스레딩 동작은 유지.
|
||||||
|
- `config_local.example.py`: `API_URL`/`PHOTO_UPLOAD_URL`을 통합 서버로, `API_KEY`는 서버와 동일.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 서버 통합 (`php/`)
|
||||||
|
|
||||||
|
| 파일 | 변경 |
|
||||||
|
|---|---|
|
||||||
|
| `api/sensor_data.php` | `leak_alert`/`leak_recovery` 수신, `is_leak` 처리, `leak_incident` 개시/종료, `leak_alert` 응답에 `log_id` 반환. **SHT30 측정 경로 100% 유지** |
|
||||||
|
| `api/photo_upload.php` *(신규)* | 멀티파트 JPEG 업로드. HMAC 서명·timestamp 신선도·`finfo` MIME·크기상한·**서버생성 무작위 파일명**·nonce 멱등·`log_id↔sensor_id` 결속 |
|
||||||
|
| `photo.php` *(신규)* | **관리자 인증** 사진 서빙. 파일명 정규식 + `realpath` 봉쇄(경로순회 방어), `readfile` image/jpeg |
|
||||||
|
| `dashboard.php` | 누수 패널(센서1 누수/정상 + `leak_incident` 이력 + `photo.php?id=` 썸네일), 이벤트 라벨 `누수감지/누수복구`, 제목 "온습도·누수 통합" |
|
||||||
|
| `sms_send.php` | `send_leak_alert_sms`(쿨다운) / `send_leak_recovery_sms`(경보 후에만) |
|
||||||
|
| `config.php` | `[누수감지]`/`[누수복구]` 태그, 쿨다운 상수, SMS 요약에 누수 카운트 |
|
||||||
|
| `var/.htaccess`, `var/photos/.htaccess` *(신규)* | mod_rewrite 무관 `Require all denied` + PHP 실행차단 |
|
||||||
|
|
||||||
|
### DB
|
||||||
|
- `sql/migration_add_leak.sql` *(신규, 가산·멱등)* — 기존 온습도 서버에 누수 복원
|
||||||
|
(`sensor_log.is_leak`/`sensor_status.is_leak`, `event_type` ENUM에 leak 값,
|
||||||
|
`leak_incident`+`leak_photo` 테이블). `sensor_metric`(SHT30) 보존. 재실행 안전.
|
||||||
|
- `sql/schema_sht30.sql` — 신규(통합) 설치용. 누수 요소 포함.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 보안 하드닝 (외부 펜테스트 대비)
|
||||||
|
|
||||||
|
**2중 독립 리뷰 → 확정 CRITICAL/HIGH = 0.**
|
||||||
|
- 광범위 감사(6도메인 → 적대 반증검증): 고위험 후보 8건 **전부 거짓양성 반증**
|
||||||
|
(TOTP 재생·CRON_SECRET 유추·setup_mfa 재등록 등 — 비번/키 선보유 필요거나 시크릿 미영속화)
|
||||||
|
- 바운티 헌터(pre-auth 원격 익스플로잇): **바운티급 0건**. SQLi/경로순회/XSS/SSRF/업로드RCE/
|
||||||
|
`var/` 노출 전부 방어 확인.
|
||||||
|
|
||||||
|
적용한 하드닝(저위험, 무회귀):
|
||||||
|
1. `cron_heartbeat.php` — `api_key_is_default()` fail-closed (신규설치 예측가능 CRON_SECRET 무력화)
|
||||||
|
2. `setup_mfa.php` — MFA 설정완료 후 부트스트랩 토큰 경로 차단
|
||||||
|
3. `var/`·`var/photos/` — PHP 실행차단(`engine off` + FilesMatch + RemoveHandler) → 업로드→RCE 폴리글랏 봉쇄
|
||||||
|
4. `php/.htaccess` — HSTS 헤더 추가
|
||||||
|
|
||||||
|
미반영(문서화): CSP `unsafe-inline` 제거는 대시보드 인라인 스크립트/스타일 nonce 리팩터
|
||||||
|
필요 → 회귀위험으로 별도단계. (현 XSS 실위험 없음 — 출력 전부 `htmlspecialchars`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 검증 내역
|
||||||
|
|
||||||
|
- **wire byte-parity:** `wire.py`(2번)·`leak_sensor.py`(1번) 모두 본문/서명이 펌웨어
|
||||||
|
`reference.py` 및 독립 HMAC과 바이트 일치(한글 UTF-8, `"`/`\` 이스케이프, round-half-up,
|
||||||
|
max uint32 ts 엣지케이스 포함). 사진 canonical 클라↔서버 동일.
|
||||||
|
- Python: 전 모듈 `py_compile` + import 그래프 + 자가점검(CRC `0xBEEF→0x92` 등) 통과.
|
||||||
|
- 적대 검증 2라운드(통합/보안) blocking 0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 배포 절차
|
||||||
|
|
||||||
|
**각 라즈베리파이4** (모듈당 1대 또는 한 대에 둘 다 — 핀 충돌 없음)
|
||||||
|
1. 해당 폴더 업로드
|
||||||
|
2. `cp config_local.example.py config_local.py` → `API_URL`/`API_KEY`/`DEVICE_ID`(+1번 `PHOTO_UPLOAD_URL`)
|
||||||
|
3. `./install.sh` → systemd enable
|
||||||
|
|
||||||
|
**cafe24 서버 (1회)**
|
||||||
|
1. DB: 기존 온습도 서버 → `sql/migration_add_leak.sql`; 신규 → `sql/schema_sht30.sql`
|
||||||
|
2. `php/` 업로드 (신규 `photo.php`, `api/photo_upload.php`, `var/.htaccess` 포함)
|
||||||
|
3. `php/var/photos/` 웹서버 쓰기권한
|
||||||
|
4. `config.local.php`의 `API_KEY` = 라즈베리파이와 동일
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 배포 전 체크리스트
|
||||||
|
|
||||||
|
- [ ] cafe24(PHP 있음)에서 문법/테스트:
|
||||||
|
`php -l php/cron_heartbeat.php php/setup_mfa.php php/dashboard.php php/photo.php php/api/photo_upload.php`
|
||||||
|
/ `php tests/security_hardening_test.php` *(작업 환경엔 PHP 없어 미실행)*
|
||||||
|
- [ ] `migration_add_leak.sql` 1회 적용
|
||||||
|
- [ ] `php/var/photos/` 쓰기권한
|
||||||
|
- [ ] 운영 위생: `API_KEY` 실값 설정 + MFA 등록 후 `MFA_SETUP_TOKEN` 비우기
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 주의 / 리스크
|
||||||
|
|
||||||
|
- ⚠️ 본 PR은 **세션 외 기존 미커밋 변경**(docs/firmware/기타 php 등)도 함께 포함(사용자 요청).
|
||||||
|
순수 이 작업분만 보려면 핵심 경로는 `raspberry_pi4*/`, `php/api/`, `php/photo.php`,
|
||||||
|
`sql/migration_add_leak.sql`, `SYSTEM_OVERVIEW.md`.
|
||||||
|
- PHP 정적 실행 검증은 작업 환경 제약으로 미수행 → 위 `php -l` 게이트 필수.
|
||||||
|
- 폐쇄망: `requests`/`smbus2` 등 의존성은 `vendor.sh`로 사전 번들(aarch64).
|
||||||
Loading…
Reference in a new issue