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 제외.
207 lines
11 KiB
Markdown
207 lines
11 KiB
Markdown
# 서버실 온습도(SHT30) 모니터링 시스템 v2606
|
|
|
|
Sensirion SHT30 온습도 센서, STM32F407VGT6(FreeRTOS) 펌웨어, Cafe24 PHP/MySQL, Cafe24 SMS를 이용한 서버실 온습도 24시간 모니터링 및 임계 초과 SMS 알림 시스템입니다. 측정값은 5분 주기로 서버에 전송되어 웹 대시보드에 표시되고, 고온/저온/고습/저습 임계를 초과하면 SMS로 통보합니다. 펌웨어는 유선 Ethernet(LAN8720 RMII) + 온칩 TLS(mbedTLS HTTPS)로 서버에 보고합니다.
|
|
|
|
## 구성
|
|
|
|
```text
|
|
[SHT30] --I2C--> [STM32F407VGT6 (sht30_fw)] --유선 Ethernet/TLS--> [Cafe24 PHP API]
|
|
|
|
|
[MySQL DB]
|
|
|
|
|
[Cafe24 SMS / Dashboard]
|
|
```
|
|
|
|
- 단일 보드 `sht30_fw` (`sensor_id=2`, `device_id=stm32-sht30-01`).
|
|
- SHT30 → STM32 I2C1(PB6 SCL / PB7 SDA, 주소 0x44, 3.3V/GND, 4.7kΩ 풀업). 상세 배선은 [wiring_diagram.md](./wiring_diagram.md) 및 [firmware/docs/HARDWARE.md](../firmware/docs/HARDWARE.md) §3 참조.
|
|
- 측정 주기: 5분(펌웨어 `APP_SHT30_REPORT_INTERVAL_SEC=300`). 최초 1회 startup, 이후 periodic.
|
|
- 인증: 펌웨어가 `X-Signature: sha256(API_KEY + 요청본문)`(raw-body) 헤더로 서명하고 서버가 검증합니다. 관리자 로그인은 비밀번호 해시 + TOTP MFA + 감사로그로 보호합니다.
|
|
|
|
## 폴더 구조
|
|
|
|
```text
|
|
docs/
|
|
README.md # 설치/운영 허브 문서
|
|
INSTALL_PI_SERVER.md # STM32 펌웨어 및 Cafe24 서버 설치 설명서
|
|
OPERATIONS_SECURITY_CHECKLIST.md # 월간 운영·보안 점검표
|
|
SECURITY_EVIDENCE_PACKAGE.md # 보안대책서 첨부자료 패키지
|
|
wiring_diagram.md # SHT30 I2C 배선도
|
|
SHT30_SENSOR2_SETUP.md # SHT30 설치 가이드(STM32 기본 / RPi 대안)
|
|
solutions/ # 실행-검토 사이클에서 얻은 교훈/반복 방지 기록
|
|
firmware/ # STM32F407VGT6 베어메탈 펌웨어 (sht30_fw)
|
|
common/ # 이식 코어(sha256/sig/jsonbody/sht30_convert/httpapi/net 등)
|
|
board_sht30/ # SHT30 보드 진입점/측정 태스크
|
|
common/secrets.h.example # APP_API_KEY 템플릿 (실 secrets.h 는 운영자가 생성, 미커밋)
|
|
certs/ # 서버 CA (운영 CA 로 교체)
|
|
docs/ # HARDWARE.md, BUILD_OFFLINE.md, PORTING_NOTES.md
|
|
php/
|
|
config.php # 공통 설정 로더(METRIC_* 임계, HEARTBEAT_TIMEOUT_SEC 등)
|
|
config.local.example.php # 서버 운영 설정 예시
|
|
ops_checks.php # 운영 점검 공통 헬퍼
|
|
sms_send.php # Cafe24 SMS 발송
|
|
cron_heartbeat.php # 장비 오프라인 감지
|
|
dashboard.php # 웹 대시보드(현재 온습도/임계 상태)
|
|
setup_wizard.php # 설치/운영 점검 화면
|
|
security_evidence.php # 보안대책서 첨부 증적 보고서
|
|
monthly_report.php # 월간 운영 보고서
|
|
login.php # 대시보드 로그인
|
|
setup_mfa.php # 관리자 TOTP MFA 등록
|
|
admin_security.php # 관리자 보안/감사 헬퍼
|
|
retention_cleanup.php # 로그 보관기간 정리
|
|
blocked.php # 차단 안내 화면
|
|
api/sensor_data.php # STM32 센서 데이터 수신
|
|
sql/
|
|
schema_sht30.sql # 신규 설치용 통합 스키마
|
|
migration_drop_leak.sql # 기존 누수 설치 → 온습도 전용 전환 마이그레이션
|
|
_backup/
|
|
pre_v2605_20260519/ # 정리 전 원본 백업
|
|
```
|
|
|
|
## 신규 설치 순서
|
|
|
|
1. 배선을 확인합니다.
|
|
|
|
[wiring_diagram.md](./wiring_diagram.md)의 SHT30 I2C 배선(PB6 SCL / PB7 SDA, 주소 0x44, 3.3V/GND, 4.7kΩ 풀업)을 적용합니다.
|
|
|
|
2. Cafe24 MySQL에 통합 스키마를 실행합니다.
|
|
|
|
```sql
|
|
SOURCE /path/to/sql/schema_sht30.sql;
|
|
```
|
|
|
|
phpMyAdmin에서 실행할 때는 `sql/schema_sht30.sql` 내용을 복사해 실행합니다. 생성 테이블은 `sensor_log`, `sensor_status`, `sensor_metric`, `sms_log`입니다.
|
|
|
|
3. PHP 파일을 업로드합니다.
|
|
|
|
```text
|
|
public_html/raspi_leck_detecter/
|
|
api/
|
|
sensor_data.php
|
|
var/.gitkeep
|
|
.htaccess
|
|
blocked.php
|
|
config.php
|
|
config.local.php
|
|
ops_checks.php
|
|
sms_send.php
|
|
cron_heartbeat.php
|
|
dashboard.php
|
|
setup_wizard.php
|
|
security_evidence.php
|
|
monthly_report.php
|
|
login.php
|
|
setup_mfa.php
|
|
admin_security.php
|
|
retention_cleanup.php
|
|
```
|
|
|
|
4. 서버 운영 설정을 만듭니다.
|
|
|
|
`php/config.local.example.php`를 `php/config.local.php`로 복사한 뒤 실제 DB, API, SMS, 관리자 해시, 관리자 TOTP 값을 입력합니다. 온습도 임계(METRIC_*)를 기관 기준에 맞게 바꾸려면 같은 파일에서 override 합니다(폐쇄망에서 펌웨어 재플래시 불필요).
|
|
|
|
관리자 비밀번호 해시는 서버 CLI에서 생성합니다.
|
|
|
|
```bash
|
|
php setup_hash.php "새비밀번호"
|
|
```
|
|
|
|
최초 관리자 MFA 등록은 `MFA_SETUP_TOKEN`을 임시로 설정한 뒤 `setup_mfa.php?token=임시토큰`에서 Google Authenticator 호환 TOTP 키를 생성·검증합니다. 등록 후 화면에 표시되는 `ADMIN_TOTP_SECRET`을 `config.local.php`에 반영하고, `MFA_SETUP_TOKEN`은 빈 값으로 바꾸거나 삭제합니다.
|
|
|
|
5. STM32 펌웨어를 빌드·플래시합니다.
|
|
|
|
폐쇄망 빌드 머신에서 의존성을 벤더링한 뒤 `sht30_fw`를 빌드합니다(상세는 [firmware/docs/BUILD_OFFLINE.md](../firmware/docs/BUILD_OFFLINE.md)).
|
|
|
|
```bash
|
|
cp firmware/common/secrets.h.example firmware/common/secrets.h
|
|
# secrets.h 의 APP_API_KEY 를 서버 config.local.php 의 API_KEY 와 동일하게 채운다.
|
|
```
|
|
|
|
`firmware/common/app_config.h`의 `APP_API_HOST`/`APP_API_PATH`와 네트워크 주소(DHCP/static)를 운영 값으로 설정하고, `firmware/certs/server_ca.c`의 자리표시자 CA를 실제 Cafe24 루트 CA로 교체합니다.
|
|
|
|
6. 서버 cron을 등록합니다.
|
|
|
|
장비 오프라인 감지를 위해 `cron_heartbeat.php`를 1분 간격으로 호출합니다.
|
|
|
|
```cron
|
|
* * * * * php /home/hosting_user/public_html/raspi_leck_detecter/cron_heartbeat.php
|
|
```
|
|
|
|
## 기존 누수 설치 → 온습도 전용 전환
|
|
|
|
1. 현재 운영 파일과 DB를 백업합니다.
|
|
2. `php/config.local.example.php` 기준으로 `config.local.php`를 갱신합니다(METRIC_* 임계 확인).
|
|
3. DB에 전환 마이그레이션을 적용합니다.
|
|
|
|
```sql
|
|
SOURCE /path/to/sql/migration_drop_leak.sql;
|
|
```
|
|
|
|
이 마이그레이션은 레거시 누수 컬럼/테이블(`leak_photo`, `leak_incident`, `is_leak` 등)을 정리하고 `sensor_metric`을 보장합니다.
|
|
4. 신규 설치라면 위 마이그레이션 대신 `sql/schema_sht30.sql`만 사용합니다.
|
|
|
|
## 이벤트 정책
|
|
|
|
| 이벤트 | 조건 | DB | SMS |
|
|
|---|---|---|---|
|
|
| `startup` | 기기 시작 첫 측정 | `sensor_log` | X |
|
|
| `periodic` | 정상 주기 측정(5분) | `sensor_metric` | X |
|
|
| 고온/저온/고습/저습 경보 | 측정값이 임계 초과 | `sensor_metric.metric_status` | O (종류별 30분 쿨다운) |
|
|
| 정상복귀 | 경보 후 히스테리시스 포함 정상 회복 | `sensor_metric` | O (1회) |
|
|
| 장비 오프라인 | 마지막 수신 후 `HEARTBEAT_TIMEOUT_SEC`(기본 1200초=20분) 초과 | `sensor_status` | O |
|
|
| 장비 복구 | 오프라인 후 재수신 | `sensor_status` | O |
|
|
|
|
## 운영 기준
|
|
|
|
- API 키, DB 비밀번호, SMS 인증값, 관리자 해시는 코드에 직접 넣지 않습니다.
|
|
- PHP 서버는 `config.local.php` 또는 환경 변수에서 운영 값을 읽습니다.
|
|
- STM32 펌웨어는 `firmware/common/secrets.h`의 `APP_API_KEY`로 raw-body 서명을 만들며, 이 값은 서버 `API_KEY`와 같아야 합니다.
|
|
- 온습도 임계는 서버 판정값입니다. 기본값: 고온 30℃ / 저온 10℃ / 고습 70% / 저습 20%, 복귀 히스테리시스 ±1℃·±3%, 동일 종류 쿨다운 1800초(30분). `php/config.php`의 `METRIC_*`로 정의하고 `config.local.php`에서 override 합니다.
|
|
- 임계 경보 SMS는 종류(고온/저온/고습/저습)별로 30분 쿨다운이 적용되어 중복 발송을 막습니다. 정상복귀 SMS는 1회만 발송합니다.
|
|
- 관리자 로그인은 비밀번호 해시 검증 후 TOTP 인증 앱의 6자리 코드를 추가 검증합니다. `ADMIN_TOTP_SECRET`이 없으면 관리자 로그인을 허용하지 않습니다.
|
|
- 로그인 성공/실패, 로그아웃, MFA 등록 검증은 `php/var/admin_audit.log`에 감사로그로 기록합니다.
|
|
- heartbeat 체크 실행 주기와 오프라인 판정 시간(`HEARTBEAT_TIMEOUT_SEC`, 기본 1200초)은 분리되어 있습니다.
|
|
- 대시보드는 위험 상태를 첫 화면에 우선 표시하고, 최신 온습도, 운영 자가진단, 30일 운영 요약을 함께 보여줍니다.
|
|
- 운영 자가진단은 API 키, 관리자 해시, SMS 수신자, 필수 테이블(`sensor_metric` 포함), 상태 파일 폴더 권한, 최근 SMS 실패를 확인합니다.
|
|
- 월간 보고서는 온습도 임계 경보, 정상복귀, 오프라인/복구, SMS 현황을 조회하고 CSV/인쇄/PDF로 출력할 수 있습니다.
|
|
- 보관기간 정리는 `php/retention_cleanup.php --dry-run`으로 삭제 대상을 먼저 확인한 뒤, 운영 백업 완료 후 `--execute`로 수행합니다.
|
|
- 실행-검토 과정에서 나온 실수와 교훈은 `docs/solutions/`에 남겨 다음 개선 사이클에서 반복하지 않습니다.
|
|
|
|
## 운영 화면
|
|
|
|
| 화면 | 경로 | 용도 |
|
|
|---|---|---|
|
|
| 대시보드 | `dashboard.php` | 현재 온습도, 임계 상태, 센서 상태, 30일 요약 |
|
|
| 설치 점검 | `setup_wizard.php` | DB/설정/권한/SMS 테스트 |
|
|
| 보안 증적 | `security_evidence.php` | 보안통제 매트릭스와 운영 점검 결과 |
|
|
| 월간 보고서 | `monthly_report.php` | 월별 운영 요약, CSV 다운로드, 인쇄/PDF |
|
|
| MFA 등록 | `setup_mfa.php` | 관리자 TOTP 인증 앱 등록 및 검증 |
|
|
|
|
## 점검 명령
|
|
|
|
서버 로그/응답 헤더를 확인합니다.
|
|
|
|
```bash
|
|
curl -I https://your-domain.example/raspi_leck_detecter/login.php
|
|
```
|
|
|
|
STM32 펌웨어는 USART3(PD8/PD9, 115200) 콘솔 로그로 네트워크/SNTP/TLS 핸드셰이크/서버 200 응답을 확인합니다.
|
|
|
|
서버 보관기간 정리 대상과 백업 증빙을 생성합니다.
|
|
|
|
```bash
|
|
php retention_cleanup.php --dry-run
|
|
php scripts/backup_evidence.php --backup-dir /path/to/backups --restore-test "YYYY-MM restore OK"
|
|
```
|
|
|
|
## 문제 해결
|
|
|
|
| 증상 | 확인 지점 |
|
|
|---|---|
|
|
| API 403 | 펌웨어 `APP_API_KEY`와 서버 `API_KEY` 일치 여부, raw-body 서명 |
|
|
| API 500 | Cafe24 PHP 오류 로그, DB 접속 정보, `sensor_metric` 등 테이블 생성 여부 |
|
|
| 로그인 불가 | `config.local.php`의 `ADMIN_PASSWORD_HASH`, `ADMIN_TOTP_SECRET`, 인증 앱 시간 동기화 |
|
|
| SMS 미발송 | Cafe24 SMS 인증값, 발신번호 등록, 잔액, `sms_log` |
|
|
| 온습도 미표시 | I2C 배선(PB6/PB7, 0x44), 4.7kΩ 풀업, `sensor_metric` 테이블, 펌웨어 측정 로그 |
|
|
| 임계 경보 미발송 | `config.php`/`config.local.php`의 `METRIC_*` 임계, `METRIC_ALERT_COOLDOWN_SEC`(30분) |
|
|
| 오프라인 알림 지연 | `HEARTBEAT_CHECK_INTERVAL_SEC`, `HEARTBEAT_TIMEOUT_SEC`(기본 1200초) |
|
|
| 대시보드 진단 경고 | `config.local.php`, DB 마이그레이션, `php/var` 권한 |
|