실제 배포는 Raspberry Pi 4(raspberry_pi4_sht30)인데 다수 문서가 STM32를 "현재 단일보드"로 서술하던 드리프트를 교정(README/SYSTEM_OVERVIEW/ DEPLOYMENT_PLAN은 이미 Pi 기준). - SHT30_SENSOR2_SETUP.md: Pi 기본 전면 재작성, STM32는 마이그레이션 대안 - README.md / wiring_diagram.md / INSTALL_PI_SERVER.md: Pi 기본으로 재구성 - ops_checks.php / security_evidence.php: /etc/sht30-sensor.env → config_local.py - api/sensor_data.php: 헤더 예시 device_id rpi4-sht30-01 - examples/*.html: device_id 교정 + 구식 env 예제 폐기 배너 - 전압 경고 정정: "5V=소자 손상"은 틀림(모듈 2.4~5.5V). Pi에서 3.3V 쓰는 이유는 Pi GPIO 3.3V 전용(5V 시 SDA/SCL 5V → Pi 손상)
146 lines
7.2 KiB
PHP
146 lines
7.2 KiB
PHP
<?php
|
|
// =============================================================================
|
|
// security_evidence.php - security-control evidence report
|
|
// =============================================================================
|
|
|
|
require_once __DIR__ . '/config.php';
|
|
require_once __DIR__ . '/admin_security.php';
|
|
require_once __DIR__ . '/ops_checks.php';
|
|
|
|
ini_set('session.cookie_httponly', 1);
|
|
ini_set('session.cookie_secure', 1);
|
|
ini_set('session.cookie_samesite', 'Strict');
|
|
ini_set('session.use_strict_mode', 1);
|
|
session_start();
|
|
|
|
if (empty($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
|
|
header('Location: login.php');
|
|
exit;
|
|
}
|
|
admin_enforce_session_timeout(); // 유휴 + 절대 수명 타임아웃(대시보드 외 페이지에서도 강제)
|
|
|
|
$checks = collect_ops_checks();
|
|
$failed_count = ops_failed_count($checks);
|
|
$generated_at = date('Y-m-d H:i:s');
|
|
|
|
$control_rows = [
|
|
['사업 범위', '서버실 온습도(SHT30) 모니터링 및 임계치 초과 SMS 경보, 장비 오프라인 알림', '보안대책서 사업개요', 'SECURITY_PLAN_PASS_READINESS.md'],
|
|
['전송구간', 'HTTPS/TLS 사용, API 서명 검증', 'api/sensor_data.php, config.php', 'SOURCE_SECURITY_EVIDENCE.md'],
|
|
['API 인증', '공유 API 키 기반 요청 서명 검증', 'config.php', '403 테스트 결과'],
|
|
['비밀값', 'config.local.php(서버)와 config_local.py(라즈베리파이)로 분리', 'config.php', '설정 파일 권한 화면'],
|
|
['관리자 인증', '비밀번호 해시, CSRF, 세션 보호, 실패 제한, 관리자 MFA', 'login.php, admin_security.php', '로그인 화면, MFA 등록 확인표'],
|
|
['감사로그', '로그인 성공/실패, 로그아웃, 관리자 활동 접속기록 저장', 'admin_security.php, php/var/admin_audit.log', '감사로그 파일'],
|
|
['로그관리', '센서 측정 이력(leak_sensor_metric)·SMS 발송 이력(leak_sms_log)·관리자 감사로그 저장 및 보관기간 정리', 'MySQL, retention_cleanup.php', '보관기간 정리 dry-run 결과'],
|
|
['장애대응', '온습도 임계 경보 SMS + 장비 오프라인/복구 알림', 'cron_heartbeat.php', 'SMS 로그, leak_sensor_metric 이력'],
|
|
['백업', 'DB/설정 백업 목록과 복구 테스트 증적 관리', 'scripts/backup_evidence.php', 'backup-evidence 보고서'],
|
|
];
|
|
|
|
function md_cell(string $value): string {
|
|
return str_replace(["\r", "\n", '|'], [' ', ' ', '/'], $value);
|
|
}
|
|
|
|
if (($_GET['format'] ?? '') === 'md') {
|
|
admin_audit_log('security_evidence_download', $_SESSION['username'] ?? ADMIN_USER, true, ['format' => 'md']);
|
|
header('Content-Type: text/markdown; charset=utf-8');
|
|
header('Content-Disposition: attachment; filename="security-evidence-' . date('Ymd-His') . '.md"');
|
|
echo "# 온습도 모니터링 시스템 보안 증적 보고서\n\n";
|
|
echo "- 생성시각: {$generated_at}\n";
|
|
echo "- 버전: " . APP_VERSION . "\n";
|
|
echo "- 평가 결과: " . ($failed_count === 0 ? "전체 통과" : "확인 필요 {$failed_count}건") . "\n\n";
|
|
echo "## 운영 점검 결과\n\n";
|
|
echo "| 분류 | 항목 | 결과 | 상세 | 조치 | 증적 |\n";
|
|
echo "|---|---|---|---|---|---|\n";
|
|
foreach ($checks as $check) {
|
|
echo '| ' . md_cell($check['category']) . ' | ' . md_cell($check['label']) . ' | ' . ($check['ok'] ? '정상' : '확인 필요') . ' | ' . md_cell($check['detail']) . ' | ' . md_cell($check['fix']) . ' | ' . md_cell($check['evidence']) . " |\n";
|
|
}
|
|
echo "\n## 보안통제 매트릭스\n\n";
|
|
echo "| 영역 | 보안대책 | 구현 위치 | 확인 증적 |\n";
|
|
echo "|---|---|---|---|\n";
|
|
foreach ($control_rows as $row) {
|
|
echo '| ' . implode(' | ', array_map('md_cell', $row)) . " |\n";
|
|
}
|
|
exit;
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>보안 증적 보고서 - 온습도 모니터링 <?= htmlspecialchars(APP_VERSION) ?></title>
|
|
<link rel="stylesheet" href="assets/app.css">
|
|
<style>
|
|
/* 보안 증적 전용 잔여 스타일 — 값은 app.css 토큰(var)만 사용 */
|
|
.evi-summary { margin-bottom: var(--sp-5); }
|
|
.evi-summary strong { font-size: 1.25rem; }
|
|
.evi-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-top: var(--sp-4); }
|
|
.evi-actions a, .evi-actions button {
|
|
display: inline-block; background: var(--primary); color: var(--on-primary);
|
|
border: 0; border-radius: var(--r-sm); padding: 8px 14px;
|
|
font-size: .8125rem; font-weight: 700; font-family: var(--font);
|
|
text-decoration: none; cursor: pointer;
|
|
}
|
|
.evi-actions a:hover, .evi-actions button:hover { background: var(--primary-600); }
|
|
@media print { .evi-actions { display: none; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>보안 증적 보고서 <?= htmlspecialchars(APP_VERSION) ?></h1>
|
|
<div class="top-actions">
|
|
<a href="dashboard.php">대시보드</a>
|
|
<a href="setup_wizard.php">설치 점검</a>
|
|
</div>
|
|
</header>
|
|
<main class="container">
|
|
<section class="panel evi-summary">
|
|
<strong class="<?= $failed_count === 0 ? 'check-ok' : 'check-fail' ?>"><?= $failed_count === 0 ? '전체 점검 통과' : "확인 필요 {$failed_count}건" ?></strong>
|
|
<p class="caption">생성시각: <?= htmlspecialchars($generated_at) ?> · 버전: <?= htmlspecialchars(APP_VERSION) ?></p>
|
|
<p class="caption">이 보고서는 보안대책서 첨부용 운영 점검 결과와 보안통제 매트릭스를 제공합니다. 비밀번호, API 키, SMS 인증키 원문은 표시하지 않습니다.</p>
|
|
<div class="evi-actions">
|
|
<a href="?format=md">Markdown 다운로드</a>
|
|
<button type="button" onclick="window.print()">인쇄/PDF</button>
|
|
</div>
|
|
</section>
|
|
|
|
<h2>운영 점검 결과</h2>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr><th>분류</th><th>항목</th><th>결과</th><th>상세</th><th>조치</th><th>증적</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($checks as $check): ?>
|
|
<tr>
|
|
<td><?= htmlspecialchars($check['category']) ?></td>
|
|
<td><?= htmlspecialchars($check['label']) ?></td>
|
|
<td class="<?= $check['ok'] ? 'check-ok' : 'check-fail' ?>"><?= $check['ok'] ? '정상' : '확인 필요' ?></td>
|
|
<td><?= htmlspecialchars($check['detail']) ?></td>
|
|
<td><?= htmlspecialchars($check['fix'] ?: '-') ?></td>
|
|
<td><?= htmlspecialchars($check['evidence'] ?: '-') ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<h2>보안통제 매트릭스</h2>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr><th>영역</th><th>보안대책</th><th>구현 위치</th><th>확인 증적</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($control_rows as $row): ?>
|
|
<tr>
|
|
<?php foreach ($row as $cell): ?>
|
|
<td><?= htmlspecialchars($cell) ?></td>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</main>
|
|
</body>
|
|
</html>
|