POSA_LEAKSMS/raspberry_pi4_sht30/vendor.sh
cw2007 de51d1c3d4 fix(security): Pi 노드 하드닝 6종 medium 갭 보완 + 회귀 수정
적대적 보안 감사에서 확정된 medium 6건 보완 (critical/high 0):
- systemd 유닛(양 노드): NoNewPrivileges + ProtectSystem=strict/
  ProtectHome=read-only/ProtectKernel*/ProtectControlGroups/RestrictSUIDSGID/
  LockPersonality/RestrictRealtime/RestrictAddressFamilies(+AF_NETLINK) 샌드박싱.
- harden_pi.sh: SSH 차단을 마지막 단계([6/6])로 재정렬 — 앞 단계 실패 시 set -e 로
  SSH 가 살아있는 상태에서 중단(원격 복구 가능). nftables·unattended-upgrades 를
  오프라인(debs/ --no-download)으로도 설치 지원(폐쇄망 harden). 미발견 secret 경고.
- install.sh(양 노드): config_local.py 설치 즉시 chmod 600 (harden 전 노출창 제거).
- vendor.sh(양 노드): .deb 를 파일명이 아닌 dpkg-deb Architecture 필드로 실검사
  (arm64/all 만 허용, 그 외 hard-fail). nftables·unattended-upgrades 를 번들에 포함.

검증(3-리뷰어 워크플로)이 잡은 회귀 동반 수정:
- [critical] ProtectHome=read-only 가 lgpio 의 작업디렉터리(.lgd-nfy 파이프) 쓰기를
  막아 add_event_detect(누수 인터럽트 등록) 실패 → 데몬 크래시루프 → 감지 불능.
  Environment=LG_WD=/tmp 로 PrivateTmp 쓰기가능 /tmp 이동(누수 노드 한정, SHT30 무관).
- [medium] 오프라인 설치에 --no-install-recommends 추가(vendor --no-recommends 정합,
  네트워크 폴백 방지) — install.sh x2 + harden_pi.sh.
- SupplementaryGroups=video gpio 명시 · nft|head 에 ||true(SIGPIPE 중단 방지) ·
  online harden 분기에 apt-get update.

※ 온디바이스 런타임 테스트 1회 필요(빌드머신 Windows 에선 systemd 실행 불가):
  실 Pi 에서 systemctl start 후 add_event_detect 콜백·카메라 촬영·업로드 정상 확인.
2026-07-03 09:28:44 +09:00

75 lines
3.9 KiB
Bash

#!/usr/bin/env bash
# =============================================================================
# vendor.sh - 폐쇄망 설치용 의존성 사전 다운로드 (SHT30 2번 모듈)
# 인터넷에 연결된 빌드/스테이징 머신에서 실행한다.
# 산출물: wheelhouse/ (pip wheel + sdist), debs/ (apt .deb) → 타깃 Pi 4 로 전송.
#
# requests 는 arch 종속이 적으나 smbus2 는 순수 파이썬(py3-none-any)이라 어떤
# 빌드 머신에서 받아도 Pi 4 에서 동작한다. wheel 핀은 타깃(Bookworm 64-bit,
# aarch64, Python 3.11)에 맞춘다. .deb 는 타깃 OS/arch 에서 받는 것이 가장 안전.
# 환경변수: TARGET_PLATFORM(기본 manylinux2014_aarch64), TARGET_PYVER(기본 311)
# =============================================================================
set -euo pipefail
cd "$(dirname "$0")"
TARGET_PLATFORM="${TARGET_PLATFORM:-manylinux2014_aarch64}"
TARGET_PYVER="${TARGET_PYVER:-311}"
TARGET_ABI="${TARGET_ABI:-cp${TARGET_PYVER}}"
echo "[vendor] (1/2) Python wheel → wheelhouse/ (platform=${TARGET_PLATFORM}, py=${TARGET_PYVER})"
mkdir -p wheelhouse
# requests 등 arch 종속 wheel: 타깃 플랫폼 명시 핀.
python3 -m pip download -r requirements.txt -d wheelhouse \
--only-binary=:all: \
--platform "${TARGET_PLATFORM}" \
--python-version "${TARGET_PYVER}" \
--implementation cp \
--abi "${TARGET_ABI}" \
|| echo "[WARN] 일부 wheel 의 타깃 플랫폼 핀 실패 — 순수파이썬(any) 폴백을 아래에서 받는다."
# smbus2(py3-none-any) 등 순수 파이썬 wheel 및 sdist 폴백.
python3 -m pip download -r requirements.txt -d wheelhouse \
|| echo "[WARN] sdist/any 폴백 일부 실패 (바이너리 wheel 이 모두 있으면 무시 가능)"
WHEEL_COUNT="$(ls -A wheelhouse 2>/dev/null | wc -l)"
echo " wheelhouse 파일 수: ${WHEEL_COUNT}"
[ "${WHEEL_COUNT}" -gt 0 ] || { echo "[ERROR] wheelhouse 비어있음"; exit 1; }
echo "[vendor] (2/2) apt .deb → debs/"
mkdir -p debs
# nftables·unattended-upgrades 도 포함 → harden_pi.sh 를 폐쇄망에서 오프라인 실행 가능.
BASE_PKGS="python3-requests python3-venv i2c-tools nftables unattended-upgrades"
DEPS="$(apt-cache depends --recurse --no-recommends --no-suggests \
--no-conflicts --no-breaks --no-replaces --no-enhances ${BASE_PKGS} \
| awk '/^\w/{print $1}' | sort -u)"
( cd debs && apt-get download ${BASE_PKGS} ${DEPS} ) 2>/dev/null \
|| echo "[WARN] 일부 .deb 다운로드 실패 (가상패키지/전이 의존성일 수 있음 — 필수 패키지는 아래에서 검증)"
MISS=0
for p in ${BASE_PKGS}; do
if ! ls debs/"${p}"_*.deb >/dev/null 2>&1; then
echo "[ERROR] 필수 패키지 .deb 누락: ${p}"
MISS=1
fi
done
[ "${MISS}" = 0 ] || { echo "[ERROR] 필수 .deb 누락 — 반드시 타깃 arch(aarch64 Bookworm)에서 vendor.sh 를 실행하세요."; exit 1; }
echo " debs 파일 수: $(ls debs/*.deb 2>/dev/null | wc -l) (필수 패키지 모두 확인됨)"
# 아키텍처 실검사 — 파일명이 아니라 .deb 내부 Architecture 필드로 타깃(arm64/all) 검증.
# (호스트가 amd64면 apt-get download 가 amd64 .deb 를 받아, 타깃 Pi 에서 dpkg 가
# "wrong architecture" 로 깨진다. 파일명만 보는 검사는 이를 못 잡는다.)
BAD_ARCH=0
for deb in debs/*.deb; do
[ -e "$deb" ] || continue
arch="$(dpkg-deb --field "$deb" Architecture 2>/dev/null || echo '?')"
case "$arch" in
arm64|all) : ;;
*) echo "[ERROR] arch 불일치: $(basename "$deb") = ${arch} (arm64/all 아님)"; BAD_ARCH=1 ;;
esac
done
if [ "${BAD_ARCH}" != 0 ]; then
echo "[ERROR] 타깃(aarch64/arm64) 이 아닌 .deb 가 있습니다. 호스트 arch=$(dpkg --print-architecture 2>/dev/null || echo unknown)." >&2
echo " 반드시 arm64 Pi(또는 arm64 chroot/qemu)에서 vendor.sh 를 실행하세요." >&2
exit 1
fi
echo " arch 검사 통과 (모든 .deb = arm64/all)"
echo "[vendor] 완료. wheelhouse/ 와 debs/ 를 타깃 Pi 4 로 전송 후 install.sh 실행."