#!/usr/bin/env bash # scripts/airgap-lab-setup.sh — 60-airgap-lab 부트스트랩(멱등). dns-lab-setup.sh 계보. # # usage: # bash scripts/airgap-lab-setup.sh up # (기본) CA/인증서 생성 → 전체 적용 → DNS 레코드 기록 # bash scripts/airgap-lab-setup.sh sync # ext pod 재시작 후 fake-dns A 레코드 재동기화 # bash scripts/airgap-lab-setup.sh test # 스모크 테스트 4종(ingress http/https, egress, blocked) # bash scripts/airgap-lab-setup.sh down # 정리 명령 "출력만" — 삭제는 승인 정책(§6)에 따라 수동 # # env: # CTX=homelab kube context # REG_PREFIX= 폐쇄망 미러 prefix. 지정 시 모든 image: 참조 앞에 prepend. # (manifest의 이미지가 전부 fully-qualified라 Harbor proxy-cache # 레이아웃 "mirror.corp/docker.io/library/nginx:..." 이 그대로 나온다) set -euo pipefail CTX="${CTX:-homelab}" REG_PREFIX="${REG_PREFIX:-}" DIR="$(cd "$(dirname "$0")/.." && pwd)" SC="$DIR/scenarios/60-airgap-lab" WORK="$DIR/tmp/airgap-lab"; mkdir -p "$WORK" K="kubectl --context=$CTX" # manifest 적용 헬퍼 — REG_PREFIX가 있으면 image: 참조 앞에 미러 주소를 prepend apply_f() { if [ -n "$REG_PREFIX" ]; then sed -E "s#^([[:space:]]*image:[[:space:]]*)#\1${REG_PREFIX%/}/#" "$1" | $K apply -f - else $K apply -f "$1" fi } # fake-dns /hosts/addn 재작성 — 반드시 3줄을 "한 번에" 쓴다(부분 기록 시 나머지 레코드 소실) sync_dns() { local ip_api ip_blocked ip_ingress ip_api="$($K -n lab-ext get pod -l app=ext-a -o jsonpath='{.items[0].status.podIP}')" ip_blocked="$($K -n lab-ext get pod -l app=ext-b -o jsonpath='{.items[0].status.podIP}')" ip_ingress="$($K -n istio-system get svc istio-ingressgateway -o jsonpath='{.spec.clusterIP}')" [ -n "$ip_api" ] && [ -n "$ip_blocked" ] && [ -n "$ip_ingress" ] \ || { echo "!! pod/svc IP 조회 실패(ext-a=$ip_api ext-b=$ip_blocked ingress=$ip_ingress)"; exit 1; } $K -n lab-ext exec deploy/fake-dns -c writer -- sh -c \ "printf '%s api.lab.external\n%s blocked.lab.external\n%s www.lab.external\n' \ '$ip_api' '$ip_blocked' '$ip_ingress' > /hosts/addn" echo " api.lab.external -> $ip_api (ext-a pod IP — Service 없음, 20-ext-servers.yaml 주석 참조)" echo " blocked.lab.external -> $ip_blocked (ext-b pod IP)" echo " www.lab.external -> $ip_ingress (ingressgateway ClusterIP)" } cmd="${1:-up}" case "$cmd" in # ---------------------------------------------------------------- up up) echo "== [0] preflight ==" $K version --request-timeout=5s >/dev/null 2>&1 || { echo "!! API 도달 불가($CTX)"; exit 1; } echo "== [1] 사설 CA + 인증서 (외부 서버용 / ingress용 — 같은 CA 체인) ==" if [ ! -f "$WORK/ca.crt" ]; then openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ -keyout "$WORK/ca.key" -out "$WORK/ca.crt" -subj "/CN=Airgap Lab CA" >/dev/null 2>&1 fi if [ ! -f "$WORK/ext.crt" ]; then openssl req -newkey rsa:2048 -nodes -keyout "$WORK/ext.key" -out "$WORK/ext.csr" \ -subj "/CN=api.lab.external" >/dev/null 2>&1 openssl x509 -req -in "$WORK/ext.csr" -CA "$WORK/ca.crt" -CAkey "$WORK/ca.key" \ -CAcreateserial -days 365 -out "$WORK/ext.crt" \ -extfile <(echo "subjectAltName=DNS:api.lab.external,DNS:blocked.lab.external") >/dev/null 2>&1 fi if [ ! -f "$WORK/www.crt" ]; then openssl req -newkey rsa:2048 -nodes -keyout "$WORK/www.key" -out "$WORK/www.csr" \ -subj "/CN=www.lab.external" >/dev/null 2>&1 openssl x509 -req -in "$WORK/www.csr" -CA "$WORK/ca.crt" -CAkey "$WORK/ca.key" \ -CAcreateserial -days 365 -out "$WORK/www.crt" \ -extfile <(echo "subjectAltName=DNS:www.lab.external") >/dev/null 2>&1 fi echo "== [2] namespaces + secrets/configmaps ==" apply_f "$SC/00-namespaces.yaml" # 외부 서버 인증서(lab-ext) — nginx가 TLS를 말하기 위한 것 $K -n lab-ext create secret tls ext-tls \ --cert="$WORK/ext.crt" --key="$WORK/ext.key" --dry-run=client -o yaml | $K apply -f - # ingress 종단 인증서 — credentialName은 "gateway workload ns"(istio-system)에서 찾는다 $K -n istio-system create secret tls www-lab-tls \ --cert="$WORK/www.crt" --key="$WORK/www.key" --dry-run=client -o yaml | $K apply -f - # CA 공개키 — 클라이언트 검증용(양쪽 ns에 배포) for ns in lab-ext lab-apps; do $K -n "$ns" create configmap lab-ca \ --from-file=ca.crt="$WORK/ca.crt" --dry-run=client -o yaml | $K apply -f - done echo "== [3] fake-dns (cluster DNS ClusterIP 치환) ==" CLUSTER_DNS="$($K -n kube-system get svc coredns -o jsonpath='{.spec.clusterIP}' 2>/dev/null \ || $K -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}' 2>/dev/null)" [ -n "$CLUSTER_DNS" ] || { echo "!! cluster DNS Service(coredns/kube-dns)를 못 찾음"; exit 1; } echo " cluster DNS = $CLUSTER_DNS" if [ -n "$REG_PREFIX" ]; then sed -e "s/__CLUSTER_DNS__/$CLUSTER_DNS/g" \ -E -e "s#^([[:space:]]*image:[[:space:]]*)#\1${REG_PREFIX%/}/#" "$SC/10-fake-dns.yaml" | $K apply -f - else sed "s/__CLUSTER_DNS__/$CLUSTER_DNS/g" "$SC/10-fake-dns.yaml" | $K apply -f - fi $K -n lab-ext rollout status deploy/fake-dns --timeout=90s echo "== [4] 외부 서버 모사(ext-a/ext-b) ==" apply_f "$SC/20-ext-servers.yaml" $K -n lab-ext rollout status deploy/ext-a --timeout=90s $K -n lab-ext rollout status deploy/ext-b --timeout=90s echo "== [5] fake-dns A 레코드 기록 ==" sync_dns echo "== [6] 클라이언트 2종 (fake-dns IP + cluster domain 치환) ==" LABDNS_IP="$($K -n lab-ext get svc fake-dns -o jsonpath='{.spec.clusterIP}')" # cluster domain 자동 발견 — 커스텀 도메인 클러스터 대응(homelab은 homelab.local). # 하드코딩 시 istiod resolve 실패 → sidecar 안 뜸(50-dns 랩에서 실제 발생). CLUSTER_DOMAIN="$($K -n kube-system get cm coredns -o jsonpath='{.data.Corefile}' 2>/dev/null \ | grep -oE 'kubernetes[[:space:]]+[^ ]+' | awk '{print $2}')" CLUSTER_DOMAIN="${CLUSTER_DOMAIN:-cluster.local}" echo " fake-dns = $LABDNS_IP · cluster domain = $CLUSTER_DOMAIN" for f in 30-ext-client.yaml 40-mesh-client.yaml; do if [ -n "$REG_PREFIX" ]; then sed -e "s/__LABDNS_IP__/$LABDNS_IP/g" -e "s/__CLUSTER_DOMAIN__/$CLUSTER_DOMAIN/g" \ -E -e "s#^([[:space:]]*image:[[:space:]]*)#\1${REG_PREFIX%/}/#" "$SC/$f" | $K apply -f - else sed -e "s/__LABDNS_IP__/$LABDNS_IP/g" -e "s/__CLUSTER_DOMAIN__/$CLUSTER_DOMAIN/g" "$SC/$f" | $K apply -f - fi done $K -n lab-ext rollout status deploy/ext-client --timeout=120s $K -n lab-apps rollout status deploy/mesh-client --timeout=120s echo "== [7] ingress / egress / REGISTRY_ONLY 시나리오 ==" apply_f "$SC/50-ingress.yaml" apply_f "$SC/60-egress.yaml" apply_f "$SC/65-sidecar-registry-only.yaml" echo echo "== ready — 스모크: bash scripts/airgap-lab-setup.sh test ==" ;; # ---------------------------------------------------------------- sync sync) echo "== fake-dns A 레코드 재동기화 (ext pod 재시작 → pod IP 변경 대응) ==" sync_dns ;; # ---------------------------------------------------------------- test test) echo "== [T1] ingress HTTP — ext-client -> www.lab.external:80 (기대: web-a / /b는 web-b) ==" $K -n lab-ext exec deploy/ext-client -- curl -sS --max-time 5 http://www.lab.external/ $K -n lab-ext exec deploy/ext-client -- curl -sS --max-time 5 http://www.lab.external/b echo "== [T2] ingress HTTPS — TLS termination + 사설 CA 검증 (기대: web-a) ==" $K -n lab-ext exec deploy/ext-client -- \ curl -sS --max-time 5 --cacert /etc/lab-ca/ca.crt https://www.lab.external/ echo "== [T3] egress — mesh-client -> api.lab.external (기대: ext-a, egressgateway 경유) ==" $K -n lab-apps exec deploy/mesh-client -c netshoot -- \ curl -sS --max-time 5 --cacert /etc/lab-ca/ca.crt https://api.lab.external/ echo " 경유 확인: kubectl -n istio-system logs deploy/istio-egressgateway --tail=5" echo "== [T4] REGISTRY_ONLY — blocked.lab.external (기대: 실패 = 차단 정상) ==" if $K -n lab-apps exec deploy/mesh-client -c netshoot -- \ curl -sS --max-time 5 --cacert /etc/lab-ca/ca.crt https://blocked.lab.external/ 2>/dev/null; then echo " !! FAIL — 차단돼야 할 호출이 성공함 (65-sidecar-registry-only.yaml 적용 여부 확인)" exit 1 else echo " PASS — sidecar가 미등록 외부를 차단함(connection reset)" fi ;; # ---------------------------------------------------------------- down down) cat <