2번 모듈 신규(raspberry_pi4_sht30/): SHT30 온습도 → cafe24, raw-body X-Signature HMAC, 폐쇄망 오프라인 설치(vendor/install), systemd, CRC/포맷 자가점검. firmware sht30_fw wire 포맷과 byte-parity 검증. 1번 모듈 통합(raspberry_pi4/): 누수+카메라를 동일 서버로. 레거시 sha256(key+json) body-sig → X-Signature HMAC 전환, 사진 canonical=device_id|sensor_id|log_id|timestamp. 서버 통합(php/): api/sensor_data.php 누수 이벤트 수신, api/photo_upload.php 신규(HMAC·finfo·서버생성 파일명·nonce 멱등·sensor 결속), photo.php 인증 사진서빙, dashboard.php 누수 패널+사진, sms_send.php 누수 SMS, config.php 태그/요약. DB: sql/migration_add_leak.sql(가산·멱등), schema_sht30.sql 통합 스키마. 보안 하드닝(외부 펜테스트 대비, 확정 CRITICAL/HIGH 0): cron_heartbeat api_key fail-closed, setup_mfa 부트스트랩 토큰 post-enrollment 잠금, var/ PHP 실행차단, HSTS. 문서/테스트: SYSTEM_OVERVIEW.md, security_hardening_test.php 갱신. 참고: 사용자 요청으로 세션 외 기존 미커밋 변경도 함께 포함. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
156 lines
7.1 KiB
Bash
156 lines
7.1 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# vendor.sh - 제3자 의존성 벤더링 (NETWORKED 빌드 머신 전용)
|
|
#
|
|
# 폐쇄망(air-gapped) 타깃으로 전달하기 위해, 빌드/스테이징 머신에서 핀 고정된
|
|
# 제3자 의존성을 내려받아 SHA-256 으로 무결성을 검증한 뒤 firmware/third_party
|
|
# 아래에 펼친다. **네트워크가 허용되는 유일한 단계**다. 이후 build.ps1/CMake 는
|
|
# 네트워크 없이 빌드한다.
|
|
#
|
|
# 사용:
|
|
# firmware/scripts/vendor.sh # 표준 벤더링
|
|
# FORCE=1 firmware/scripts/vendor.sh # 기존 트리 덮어쓰기(재벤더링)
|
|
# KEEP_ARCHIVES=1 firmware/scripts/vendor.sh # 압축본 보존(_archives)
|
|
# VENDOR_STRICT=1 firmware/scripts/vendor.sh # 핀(SHA-256) 미설정 시 실패(릴리스용)
|
|
#
|
|
# 요구 도구: curl(또는 wget), sha256sum(또는 shasum), unzip, tar.
|
|
#
|
|
# 중요: 아래 DEPS 의 URL/SHA256/TAG 는 운영에서 쓸 정확한 핀 버전으로 교체할 것.
|
|
# SHA256 이 'TODO' 인 항목은 검증을 생략하고 계산된 해시를 출력만 한다
|
|
# (최초 1회 기록용). 기록 후 반드시 채워 넣어 재현성을 고정한다.
|
|
# TODO(vendor): 핀 버전 확정.
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
FW_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
THIRD_PARTY="${FW_ROOT}/third_party"
|
|
ARCHIVE_DIR="${THIRD_PARTY}/_archives"
|
|
|
|
FORCE="${FORCE:-0}"
|
|
KEEP_ARCHIVES="${KEEP_ARCHIVES:-0}"
|
|
# 엄격 핀 모드: SHA-256 이 'TODO'(미설정)인 의존성이 있으면 즉시 실패한다. 릴리스
|
|
# 빌드는 VENDOR_STRICT=1 로 실행해 모든 핀이 채워졌는지(재현성 고정) 강제한다.
|
|
VENDOR_STRICT="${VENDOR_STRICT:-0}"
|
|
|
|
mkdir -p "${THIRD_PARTY}"
|
|
[ "${KEEP_ARCHIVES}" = "1" ] && mkdir -p "${ARCHIVE_DIR}"
|
|
|
|
# ── 핀 고정 의존성 (name|tag|url|sha256) ─────────────────────────────────────
|
|
# name : third_party 하위 디렉터리명 (CMakeLists 경로와 일치)
|
|
# tag : 릴리스/태그(추적용)
|
|
# url : 다운로드 URL
|
|
# sha256 : 압축 파일 SHA-256(소문자). 'TODO' 면 검증 생략(해시 출력만).
|
|
# TODO(vendor): 아래 값들을 정확한 핀 버전으로 교체.
|
|
DEPS=(
|
|
"STM32CubeF4|v1.28.0|https://github.com/STMicroelectronics/STM32CubeF4/archive/refs/tags/v1.28.0.zip|TODO"
|
|
"FreeRTOS-Kernel|V11.1.0|https://github.com/FreeRTOS/FreeRTOS-Kernel/archive/refs/tags/V11.1.0.zip|TODO"
|
|
"lwip|STABLE-2_2_0_RELEASE|https://github.com/lwip-tcpip/lwip/archive/refs/tags/STABLE-2_2_0_RELEASE.zip|TODO"
|
|
"mbedtls|v3.6.2|https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.2/mbedtls-3.6.2.tar.bz2|TODO"
|
|
)
|
|
|
|
# ── 헬퍼: 다운로드 ───────────────────────────────────────────────────────────
|
|
download() { # url out
|
|
local url="$1" out="$2"
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -fL --proto '=https' --tlsv1.2 -o "${out}" "${url}"
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
wget --https-only -O "${out}" "${url}"
|
|
else
|
|
echo "ERROR: curl 또는 wget 이 필요합니다." >&2; exit 1
|
|
fi
|
|
}
|
|
|
|
# ── 헬퍼: SHA-256 ────────────────────────────────────────────────────────────
|
|
sha256_of() { # path -> stdout(소문자 hex)
|
|
local p="$1"
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "${p}" | awk '{print tolower($1)}'
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
shasum -a 256 "${p}" | awk '{print tolower($1)}'
|
|
else
|
|
echo "ERROR: sha256sum 또는 shasum 이 필요합니다." >&2; exit 1
|
|
fi
|
|
}
|
|
|
|
# ── 헬퍼: 압축 해제(zip / tar.*) ─────────────────────────────────────────────
|
|
extract() { # archive dest
|
|
local archive="$1" dest="$2"
|
|
mkdir -p "${dest}"
|
|
case "${archive}" in
|
|
*.zip)
|
|
command -v unzip >/dev/null 2>&1 || { echo "ERROR: unzip 필요" >&2; exit 1; }
|
|
unzip -q "${archive}" -d "${dest}" ;;
|
|
*.tar.bz2|*.tbz2|*.tar.gz|*.tgz|*.tar.xz)
|
|
tar -xf "${archive}" -C "${dest}" ;;
|
|
*)
|
|
echo "ERROR: 지원하지 않는 압축 형식: ${archive}" >&2; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
for entry in "${DEPS[@]}"; do
|
|
IFS='|' read -r name tag url sha256 <<< "${entry}"
|
|
target="${THIRD_PARTY}/${name}"
|
|
|
|
echo "──────────────────────────────────────────────"
|
|
echo " 벤더링: ${name} (${tag})"
|
|
|
|
if [ -d "${target}" ] && [ "${FORCE}" != "1" ]; then
|
|
echo " 이미 존재 — 건너뜀(재벤더링은 FORCE=1). ${target}"
|
|
continue
|
|
fi
|
|
rm -rf "${target}"
|
|
|
|
tmp="$(mktemp -d)"
|
|
trap 'rm -rf "${tmp}"' EXIT
|
|
fname="$(basename "${url%%\?*}")"
|
|
archive="${tmp}/${fname}"
|
|
|
|
echo " 다운로드: ${url}"
|
|
download "${url}" "${archive}"
|
|
|
|
# ── 무결성 검증 ────────────────────────────────────────────────────────────
|
|
actual="$(sha256_of "${archive}")"
|
|
if [ "${sha256}" = "TODO" ] || [ -z "${sha256}" ]; then
|
|
if [ "${VENDOR_STRICT}" = "1" ]; then
|
|
echo " ERROR: VENDOR_STRICT=1 인데 ${name} 의 SHA-256 핀이 미설정(TODO)입니다." >&2
|
|
echo " 아래 계산값을 DEPS 에 기록한 뒤 다시 실행하세요(재현성 고정):" >&2
|
|
echo " ${name} : ${actual}" >&2
|
|
exit 1
|
|
fi
|
|
echo " [경고] SHA-256 미설정 — 검증 생략. 계산값을 기록하세요:"
|
|
echo " ${name} : ${actual}"
|
|
elif [ "${actual}" != "$(echo "${sha256}" | tr 'A-Z' 'a-z')" ]; then
|
|
echo " ERROR: SHA-256 불일치(${name})" >&2
|
|
echo " 기대: ${sha256}" >&2
|
|
echo " 실제: ${actual}" >&2
|
|
exit 1
|
|
else
|
|
echo " SHA-256 확인됨: ${actual}"
|
|
fi
|
|
|
|
# ── 해제 + 최상위 단일 폴더 벗기기 ────────────────────────────────────────
|
|
ex="${tmp}/x"
|
|
extract "${archive}" "${ex}"
|
|
# 최상위가 단일 디렉터리면 그 내용을 target 으로, 아니면 ex 자체를 target 으로.
|
|
shopt -s nullglob
|
|
tops=("${ex}"/*)
|
|
shopt -u nullglob
|
|
if [ "${#tops[@]}" -eq 1 ] && [ -d "${tops[0]}" ]; then
|
|
mv "${tops[0]}" "${target}"
|
|
else
|
|
mv "${ex}" "${target}"
|
|
fi
|
|
|
|
if [ "${KEEP_ARCHIVES}" = "1" ]; then
|
|
cp -f "${archive}" "${ARCHIVE_DIR}/${fname}"
|
|
fi
|
|
|
|
rm -rf "${tmp}"
|
|
trap - EXIT
|
|
echo " 완료 → ${target}"
|
|
done
|
|
|
|
echo "──────────────────────────────────────────────"
|
|
echo " 벤더링 완료. 이제 폐쇄망 빌드 가능."
|
|
echo " TODO(vendor): 위 출력된 SHA-256 값을 DEPS 에 채워 재현성을 고정하세요."
|