feat(deploy): 하드닝 후 현장 콘솔용 고정 IP 설정 헬퍼(set_network.sh)
harden_pi.sh 실행 후 SSH·콘솔 자동로그인이 꺼져, 네트워크 재설정은 현장 물리 콘솔에서 해야 한다. NetworkManager(nmcli) 기반으로 고정 IP/서브넷(CIDR)/ 게이트웨이/DNS 를 한 번에 설정하는 헬퍼를 추가한다. - 대화형/비대화형(--ip/--gw/--dns/--yes), 현재 확인(--show), DHCP 복귀(--dhcp). - 유선 연결/인터페이스 자동 감지, IPv4·CIDR·게이트웨이 서브넷 검증, 적용 전 이전 설정 백업, 적용 후 IP/라우팅/게이트웨이 ping/서버 test_connection 검증. - 하드닝 방화벽 불변(인바운드 drop/아웃바운드 accept라 고정 IP로 서버 POST 정상). - 폐쇄망 안전: nmcli 로컬 설정만, 네트워크 미사용. 배포 스테이징(deploy/*)은 .gitignore 대상이라 정본은 tools/ 에 둔다.
This commit is contained in:
parent
7fa39406d3
commit
79af8ef530
1 changed files with 181 additions and 0 deletions
181
tools/set_network.sh
Normal file
181
tools/set_network.sh
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# set_network.sh — 센서 노드 유선 고정 IP 설정 (하드닝 후 현장 콘솔용)
|
||||||
|
#
|
||||||
|
# 배경: harden_pi.sh 실행 후 SSH·콘솔 자동로그인이 꺼진다. 그래서 이 스크립트는
|
||||||
|
# "현장 물리 콘솔(모니터+키보드)에서 pi 로그인 후" 실행하는 것을 전제로 한다.
|
||||||
|
# Raspberry Pi OS Bookworm 기본 네트워크 관리자는 NetworkManager(nmcli)다.
|
||||||
|
#
|
||||||
|
# 하드닝 방화벽과의 관계: nftables 는 인바운드만 drop, 아웃바운드는 accept +
|
||||||
|
# established/related 허용. 고정 IP 로 바꿔도 서버 POST(아웃바운드)는 그대로
|
||||||
|
# 나가며 인바운드 규칙 추가가 필요없다. 이 스크립트는 방화벽을 건드리지 않는다.
|
||||||
|
#
|
||||||
|
# 폐쇄망(air-gapped): 네트워크 미사용. nmcli 로컬 설정만 수행한다.
|
||||||
|
#
|
||||||
|
# 사용:
|
||||||
|
# 대화형: sudo bash set_network.sh
|
||||||
|
# 비대화형: sudo bash set_network.sh --ip 192.168.0.50/24 --gw 192.168.0.1 \
|
||||||
|
# --dns "8.8.8.8 1.1.1.1" --yes
|
||||||
|
# 현재 확인: sudo bash set_network.sh --show
|
||||||
|
# DHCP 복귀: sudo bash set_network.sh --dhcp --yes
|
||||||
|
#
|
||||||
|
# 옵션:
|
||||||
|
# --ip A.B.C.D/PREFIX 고정 IP + 서브넷(CIDR). 예: 192.168.0.50/24 (=255.255.255.0)
|
||||||
|
# --gw A.B.C.D 게이트웨이
|
||||||
|
# --dns "A.B.C.D ..." DNS (공백 구분, 여러 개 가능). 서버가 도메인이면 필수.
|
||||||
|
# --iface NAME 인터페이스 (기본: 자동 감지, 보통 eth0)
|
||||||
|
# --con NAME NM 연결 이름 (기본: 유선 연결 자동 감지)
|
||||||
|
# --dhcp 수동 IP 해제 → 자동(DHCP) 복귀
|
||||||
|
# --show 현재 설정만 출력하고 종료
|
||||||
|
# --yes | -y 확인 프롬프트 건너뜀(비대화형)
|
||||||
|
#
|
||||||
|
# 서브넷 = CIDR 접미사: /24=255.255.255.0 /16=255.255.0.0 /25=255.255.255.128
|
||||||
|
# =============================================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
die(){ echo "[ERROR] $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
[ "$(id -u)" -eq 0 ] || die "sudo 로 실행하세요: sudo bash set_network.sh"
|
||||||
|
# 스크립트 폴더 기준으로 test_connection.py 등을 찾도록 cwd 이동.
|
||||||
|
cd "$(dirname "$0")" 2>/dev/null || true
|
||||||
|
|
||||||
|
command -v nmcli >/dev/null 2>&1 || die \
|
||||||
|
"nmcli(NetworkManager) 없음. Bookworm 기본이 아니면 네트워크 관리자를 확인하세요(dhcpcd/systemd-networkd)."
|
||||||
|
|
||||||
|
# ---- 인자 파싱 -------------------------------------------------------------
|
||||||
|
IPCIDR="" GW="" DNS="" IFACE="" CON="" ASSUME_YES=0 MODE="static"
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--ip) IPCIDR="${2:-}"; shift 2;;
|
||||||
|
--gw) GW="${2:-}"; shift 2;;
|
||||||
|
--dns) DNS="${2:-}"; shift 2;;
|
||||||
|
--iface) IFACE="${2:-}"; shift 2;;
|
||||||
|
--con) CON="${2:-}"; shift 2;;
|
||||||
|
--dhcp) MODE="dhcp"; shift;;
|
||||||
|
--show) MODE="show"; shift;;
|
||||||
|
--yes|-y) ASSUME_YES=1; shift;;
|
||||||
|
-h|--help) sed -n '2,40p' "$0"; exit 0;;
|
||||||
|
*) die "알 수 없는 옵션: $1 (도움말: --help)";;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---- 검증 헬퍼 -------------------------------------------------------------
|
||||||
|
valid_ipv4(){
|
||||||
|
local ip="$1" o
|
||||||
|
[[ "$ip" =~ ^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$ ]] || return 1
|
||||||
|
for o in "${BASH_REMATCH[@]:1:4}"; do [ "$o" -le 255 ] || return 1; done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
valid_cidr(){
|
||||||
|
[[ "$1" == */* ]] || return 1
|
||||||
|
local ip="${1%/*}" pfx="${1#*/}"
|
||||||
|
valid_ipv4 "$ip" || return 1
|
||||||
|
[[ "$pfx" =~ ^[0-9]+$ ]] && [ "$pfx" -ge 0 ] && [ "$pfx" -le 32 ]
|
||||||
|
}
|
||||||
|
ip2int(){ local a b c d; IFS=. read -r a b c d <<<"$1"; echo $(( (a<<24)|(b<<16)|(c<<8)|d )); }
|
||||||
|
same_subnet(){ # $1=ip $2=gw $3=prefix — 게이트웨이가 IP 와 같은 서브넷인지
|
||||||
|
local m=$(( (0xFFFFFFFF << (32-$3)) & 0xFFFFFFFF ))
|
||||||
|
[ $(( $(ip2int "$1") & m )) -eq $(( $(ip2int "$2") & m )) ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- 유선 연결/인터페이스 자동 감지 ---------------------------------------
|
||||||
|
if [ -z "$IFACE" ]; then
|
||||||
|
IFACE="$(nmcli -t -f DEVICE,TYPE dev status 2>/dev/null | awk -F: '$2=="ethernet"{print $1; exit}')"
|
||||||
|
IFACE="${IFACE:-eth0}"
|
||||||
|
fi
|
||||||
|
if [ -z "$CON" ]; then
|
||||||
|
# 해당 인터페이스에 물린 활성 연결 우선 → 없으면 첫 유선 연결 → 최후 기본 이름.
|
||||||
|
CON="$(nmcli -t -f NAME,DEVICE con show --active 2>/dev/null | awk -F: -v d="$IFACE" '$2==d{print $1; exit}')"
|
||||||
|
[ -n "$CON" ] || CON="$(nmcli -t -f NAME,TYPE con show 2>/dev/null | awk -F: '$2=="802-3-ethernet"{print $1; exit}')"
|
||||||
|
[ -n "$CON" ] || CON="Wired connection 1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_current(){
|
||||||
|
echo "== 인터페이스: $IFACE / 연결: $CON =="
|
||||||
|
nmcli -f ipv4.method,ipv4.addresses,ipv4.gateway,ipv4.dns con show "$CON" 2>/dev/null \
|
||||||
|
|| echo " (연결 '$CON' 없음)"
|
||||||
|
echo "-- 실제 IP:"; ip -4 addr show "$IFACE" 2>/dev/null | awk '/inet /{print " "$2}' || true
|
||||||
|
echo "-- 라우팅:"; ip route 2>/dev/null | sed 's/^/ /' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- --show : 현재 상태만 출력 --------------------------------------------
|
||||||
|
if [ "$MODE" = show ]; then show_current; exit 0; fi
|
||||||
|
|
||||||
|
# ---- --dhcp : 자동 복귀 ----------------------------------------------------
|
||||||
|
if [ "$MODE" = dhcp ]; then
|
||||||
|
echo "연결 '$CON' → 자동(DHCP) 복귀."
|
||||||
|
[ "$ASSUME_YES" = 1 ] || { read -r -p "진행? [y/N] " a; [[ "$a" =~ ^[Yy]$ ]] || die "취소됨"; }
|
||||||
|
nmcli con mod "$CON" ipv4.method auto ipv4.addresses "" ipv4.gateway "" ipv4.dns "" ipv4.ignore-auto-dns no
|
||||||
|
nmcli con down "$CON" >/dev/null 2>&1 || true
|
||||||
|
nmcli con up "$CON" || die "활성화 실패 — 물리 링크/스위치 확인."
|
||||||
|
echo "── 결과 ──"; show_current
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- static : 대화형 프롬프트(값 누락 + TTY 인 경우만) --------------------
|
||||||
|
if [ -t 0 ]; then
|
||||||
|
[ -n "$IPCIDR" ] || read -r -p "고정 IP/서브넷 (예 192.168.0.50/24): " IPCIDR
|
||||||
|
[ -n "$GW" ] || read -r -p "게이트웨이 (예 192.168.0.1): " GW
|
||||||
|
[ -n "$DNS" ] || read -r -p "DNS (공백구분, 예 '8.8.8.8 1.1.1.1', 없으면 엔터): " DNS
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 값 검증 ---------------------------------------------------------------
|
||||||
|
valid_cidr "$IPCIDR" || die "IP/서브넷 형식 오류: '$IPCIDR' (예: 192.168.0.50/24)"
|
||||||
|
valid_ipv4 "$GW" || die "게이트웨이 형식 오류: '$GW'"
|
||||||
|
IPADDR="${IPCIDR%/*}"; PREFIX="${IPCIDR#*/}"
|
||||||
|
same_subnet "$IPADDR" "$GW" "$PREFIX" \
|
||||||
|
|| die "게이트웨이 $GW 가 $IPCIDR 서브넷 밖입니다. IP·서브넷·게이트웨이 값을 확인하세요."
|
||||||
|
for d in $DNS; do valid_ipv4 "$d" || die "DNS 형식 오류: '$d'"; done
|
||||||
|
[ -n "$DNS" ] || echo "[WARN] DNS 미지정 — 서버 주소가 도메인이면 이름해석 실패. 서버가 IP면 무방."
|
||||||
|
|
||||||
|
# ---- 요약 + 확인 -----------------------------------------------------------
|
||||||
|
echo "───────────── 적용 예정 ─────────────"
|
||||||
|
echo " 인터페이스 : $IFACE"
|
||||||
|
echo " 연결 이름 : $CON"
|
||||||
|
echo " IP/서브넷 : $IPCIDR"
|
||||||
|
echo " 게이트웨이 : $GW"
|
||||||
|
echo " DNS : ${DNS:-(없음)}"
|
||||||
|
echo "─────────────────────────────────────"
|
||||||
|
[ "$ASSUME_YES" = 1 ] || { read -r -p "적용? [y/N] " a; [[ "$a" =~ ^[Yy]$ ]] || die "취소됨"; }
|
||||||
|
|
||||||
|
# ---- 이전 설정 백업(복구용) ------------------------------------------------
|
||||||
|
BK="/root/net_backup_$(date +%Y%m%d_%H%M%S).txt"
|
||||||
|
{ echo "# '$CON' 이전 설정 (복구 참고 — $(date))"; nmcli con show "$CON" 2>/dev/null | grep -E '^ipv4\.'; } \
|
||||||
|
> "$BK" 2>/dev/null || true
|
||||||
|
echo "[INFO] 이전 설정 백업: $BK"
|
||||||
|
|
||||||
|
# ---- 적용 ------------------------------------------------------------------
|
||||||
|
echo "[적용] nmcli 로 고정 IP 설정..."
|
||||||
|
nmcli con mod "$CON" \
|
||||||
|
ipv4.method manual \
|
||||||
|
ipv4.addresses "$IPCIDR" \
|
||||||
|
ipv4.gateway "$GW" \
|
||||||
|
ipv4.dns "$DNS" \
|
||||||
|
ipv4.ignore-auto-dns yes
|
||||||
|
# 재적용(down 실패는 무시 — 최초 활성화 전일 수 있음)
|
||||||
|
nmcli con down "$CON" >/dev/null 2>&1 || true
|
||||||
|
nmcli con up "$CON" \
|
||||||
|
|| die "활성화 실패. 복구: 'sudo bash set_network.sh --dhcp --yes' 또는 백업($BK) 참고."
|
||||||
|
|
||||||
|
# ---- 검증 ------------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "───────────── 검증 ─────────────"
|
||||||
|
echo "-- IP:"; ip -4 addr show "$IFACE" 2>/dev/null | awk '/inet /{print " "$2}' || true
|
||||||
|
echo "-- 라우팅:"; ip route 2>/dev/null | sed 's/^/ /' || true
|
||||||
|
echo "-- 게이트웨이 ping:"
|
||||||
|
if ping -c2 -W2 "$GW" >/dev/null 2>&1; then echo " OK ($GW 도달)"; else \
|
||||||
|
echo " [WARN] $GW 무응답 — 배선/스위치/게이트웨이 값 확인"; fi
|
||||||
|
|
||||||
|
# 서버 연결 최종 확인 (test_connection.py 가 있으면 실행)
|
||||||
|
for t in ./test_connection.py "$HOME/raspberry_pi4/test_connection.py" "$HOME/raspberry_pi4_sht30/test_connection.py"; do
|
||||||
|
if [ -f "$t" ]; then
|
||||||
|
echo "-- 서버 연결 테스트: $t"
|
||||||
|
( cd "$(dirname "$t")" && python3 "$(basename "$t")" ) \
|
||||||
|
|| echo " [WARN] 서버 테스트 실패 — DNS/게이트웨이/서버주소 확인"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "완료. 문제 시 복구: 백업($BK) 참고하거나 sudo bash set_network.sh --dhcp --yes (DHCP 복귀)."
|
||||||
|
echo "설정 확인: sudo bash set_network.sh --show"
|
||||||
Loading…
Reference in a new issue