POSA_LEAKSMS/firmware/scripts/vendor.ps1
유창욱 90f121e14c chore: import codebase with security hardening
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 제외.
2026-06-20 09:37:40 +09:00

177 lines
7.7 KiB
PowerShell

<#
.SYNOPSIS
제3자 의존성 벤더링 (NETWORKED 빌드 머신 전용) — firmware/third_party 채우기.
.DESCRIPTION
폐쇄망(air-gapped) 타깃으로 전달하기 위해, 빌드/스테이징 머신에서 핀 고정된
제3자 의존성을 내려받아 SHA-256 으로 무결성을 검증한 뒤 firmware/third_party
아래에 펼친다. **네트워크가 허용되는 유일한 단계**다. 이후 build.ps1 은
네트워크 없이 빌드한다.
벤더링 대상(핀 고정):
- STM32CubeF4 (HAL + CMSIS + startup/system) TODO(vendor): 버전 핀 확정
- FreeRTOS-Kernel (네이티브 API, ARM_CM4F 포트) TODO(vendor)
- lwip (LwIP TCP/IP + apps/sntp) TODO(vendor)
- mbedtls (TLS 1.2 클라이언트) TODO(vendor)
중요: 아래 $Deps 의 Url/Sha256/Tag 는 운영 환경에서 **사용할 정확한 핀 버전**으로
교체해야 한다. SHA-256 미설정('TODO') 인 항목은 다운로드 후 계산된 해시를
출력만 하고(최초 1회 기록용) 검증은 건너뛴다 — 기록 후 반드시 채워 넣을 것.
.PARAMETER Force
이미 존재하는 third_party 하위 트리를 덮어쓴다(재벤더링).
.PARAMETER KeepArchives
내려받은 압축 파일을 third_party/_archives 에 보존한다(오프라인 재현용).
.EXAMPLE
pwsh firmware/scripts/vendor.ps1
pwsh firmware/scripts/vendor.ps1 -Force -KeepArchives
#>
[CmdletBinding()]
param(
[switch]$Force,
[switch]$KeepArchives
)
$ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$FwRoot = Split-Path -Parent $ScriptDir
$ThirdParty = Join-Path $FwRoot 'third_party'
$ArchiveDir = Join-Path $ThirdParty '_archives'
# =============================================================================
# 핀 고정 의존성 목록
# Name : third_party 아래 디렉터리명 (CMakeLists 의 경로와 일치해야 함)
# Tag : 릴리스/태그(추적용)
# Url : 다운로드 URL (release tarball/zip)
# Sha256 : 압축 파일 SHA-256 (소문자 hex). 'TODO' 면 검증 생략(해시 출력만).
# Strip : 압축 최상위 폴더를 벗겨낼지(대부분 release 는 단일 최상위 폴더)
#
# TODO(vendor): Url/Sha256/Tag 를 운영에서 쓸 정확한 핀 버전으로 교체.
# STM32CubeF4 는 GitHub release zip 또는 ST 공식 배포본을 사용한다.
# =============================================================================
$Deps = @(
@{
Name = 'STM32CubeF4'
Tag = 'v1.28.0'
Url = 'https://github.com/STMicroelectronics/STM32CubeF4/archive/refs/tags/v1.28.0.zip'
Sha256 = 'TODO' # TODO(vendor): 실제 zip SHA-256 기입
Strip = $true
},
@{
Name = 'FreeRTOS-Kernel'
Tag = 'V11.1.0'
Url = 'https://github.com/FreeRTOS/FreeRTOS-Kernel/archive/refs/tags/V11.1.0.zip'
Sha256 = 'TODO' # TODO(vendor)
Strip = $true
},
@{
Name = 'lwip'
Tag = 'STABLE-2_2_0_RELEASE'
Url = 'https://github.com/lwip-tcpip/lwip/archive/refs/tags/STABLE-2_2_0_RELEASE.zip'
Sha256 = 'TODO' # TODO(vendor)
Strip = $true
},
@{
Name = 'mbedtls'
Tag = 'v3.6.2'
Url = 'https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.2/mbedtls-3.6.2.tar.bz2'
Sha256 = 'TODO' # TODO(vendor): release tarball 은 공식 SHA-256 제공됨
Strip = $true
}
)
New-Item -ItemType Directory -Force -Path $ThirdParty | Out-Null
if ($KeepArchives) { New-Item -ItemType Directory -Force -Path $ArchiveDir | Out-Null }
function Get-FileSha256([string]$path) {
(Get-FileHash -Algorithm SHA256 -Path $path).Hash.ToLowerInvariant()
}
# 압축 해제(zip / tar.bz2 / tar.gz). tar 는 Windows 10+ 내장 bsdtar 사용.
function Expand-Archive2([string]$archive, [string]$dest) {
New-Item -ItemType Directory -Force -Path $dest | Out-Null
if ($archive -match '\.zip$') {
Expand-Archive -Path $archive -DestinationPath $dest -Force
}
elseif ($archive -match '\.(tar\.bz2|tbz2|tar\.gz|tgz|tar\.xz)$') {
if (-not (Get-Command 'tar' -ErrorAction SilentlyContinue)) {
throw "tar 가 필요합니다(.tar.* 해제). Windows 10+ 내장 tar 또는 git tar 사용."
}
& tar -xf $archive -C $dest
if ($LASTEXITCODE -ne 0) { throw "tar 해제 실패: $archive" }
}
else {
throw "지원하지 않는 압축 형식: $archive"
}
}
foreach ($dep in $Deps) {
$name = $dep.Name
$target = Join-Path $ThirdParty $name
Write-Host "──────────────────────────────────────────────" -ForegroundColor Cyan
Write-Host " 벤더링: $name ($($dep.Tag))" -ForegroundColor Cyan
if ((Test-Path $target) -and -not $Force) {
Write-Host " 이미 존재 — 건너뜀(재벤더링은 -Force). $target" -ForegroundColor Yellow
continue
}
if (Test-Path $target) {
Remove-Item -Recurse -Force $target
}
# ── 다운로드 ─────────────────────────────────────────────────────────────
$tmp = New-Item -ItemType Directory -Force `
-Path (Join-Path ([IO.Path]::GetTempPath()) ("vendor_" + [Guid]::NewGuid()))
$fileName = [IO.Path]::GetFileName(([Uri]$dep.Url).AbsolutePath)
$archive = Join-Path $tmp $fileName
Write-Host " 다운로드: $($dep.Url)"
Invoke-WebRequest -Uri $dep.Url -OutFile $archive -UseBasicParsing
# ── 무결성 검증(SHA-256) ────────────────────────────────────────────────
$actual = Get-FileSha256 $archive
if ($dep.Sha256 -eq 'TODO' -or [string]::IsNullOrWhiteSpace($dep.Sha256)) {
Write-Host " [경고] SHA-256 미설정 — 검증 생략. 계산값을 기록하세요:" -ForegroundColor Yellow
Write-Host " $name : $actual" -ForegroundColor Yellow
}
elseif ($actual -ne $dep.Sha256.ToLowerInvariant()) {
throw "SHA-256 불일치($name)`n 기대: $($dep.Sha256)`n 실제: $actual"
}
else {
Write-Host " SHA-256 확인됨: $actual" -ForegroundColor Green
}
# ── 해제 + 최상위 폴더 벗기기 ────────────────────────────────────────────
$extract = Join-Path $tmp 'x'
Expand-Archive2 $archive $extract
if ($dep.Strip) {
$top = @(Get-ChildItem -Directory $extract)
if ($top.Count -eq 1) {
Move-Item -Path $top[0].FullName -Destination $target
}
else {
# 최상위가 단일 폴더가 아니면 그대로 이동.
Move-Item -Path $extract -Destination $target
}
}
else {
Move-Item -Path $extract -Destination $target
}
if ($KeepArchives) {
Copy-Item -Path $archive -Destination (Join-Path $ArchiveDir $fileName) -Force
}
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
Write-Host " 완료 → $target" -ForegroundColor Green
}
Write-Host "──────────────────────────────────────────────" -ForegroundColor Cyan
Write-Host " 벤더링 완료. 이제 폐쇄망 빌드 가능: pwsh firmware/scripts/build.ps1" -ForegroundColor Green
Write-Host " TODO(vendor): 위 출력된 SHA-256 값을 \$Deps 에 채워 재현성을 고정하세요." -ForegroundColor Yellow