images.hyperbook.com EC2 역방향 프록시 전환 — Chrome PNA 정책 대응 설계 변경 보고서
초록
images.hyperbook.com DNS A 레코드를 hb5u Tailscale 사설 IP(100.125.27.70)에서 EC2 공인 IP(3.34.102.89)로 전환하고 EC2 nginx 역방향 프록시를 구성한 설계 변경 보고서. Chrome 94+ Private Network Access 정책으로 인해 thesis 논문 내 이미지가 net::ERR_FAILED로 차단되는 문제를 해결했다. 파일 저장과 업로드 경로는 hb5u 그대로 유지된다. v4: Codezy headless Chrome 렌더링 검증 완료(GET 200, naturalWidth=1440, 실패 0건), cmg-cv16 로컬 DNS 캐시 이슈 기록, EC2 nginx 운영 주의사항 추가. 완전 종결.
1. 배경 및 문제 제기
1.1 기존 구조
images.hyperbook.com은 hb5u(Samsung Galaxy Book Pro 5 Ultra)에서 직접 서빙되던 정적 파일 서버였다.
| 항목 | 값 |
|---|---|
| DNS A 레코드 | 100.125.27.70 (Tailscale 사설 IP) |
| 실제 서버 | hb5u nginx (port 443 SSL) |
| 파일 저장 경로 | /home/moos/dev_ws/images/ |
1.2 발견된 문제 — Chrome Private Network Access(PNA) 차단
2026-09-09, Codezy(cmg-cv16)가 thesis.hyperbook.com 논문에 이미지를 삽입하는 과정에서 문제를 발견했다.
증상:
- https://images.hyperbook.com/codezy/파일명.png 직접 탐색: HTTP 200 정상
- thesis.hyperbook.com 문서 내 <img> 태그로 동일 URL 로드: net::ERR_FAILED, naturalWidth=0
근본 원인: Chrome의 Private Network Access(PNA) 정책 (Chrome 94+)
images.hyperbook.com이 Tailscale 사설 IP(100.125.27.70, 100.64.0.0/10 대역)로 해석되었고, Chrome은 공개 HTTPS 페이지(thesis.hyperbook.com)에서 사설 IP 리소스를 로드하는 것을 네트워크 레벨에서 차단한다.
1.3 시도한 해결책과 한계
1차 시도: CORS + PNA 응답 헤더 추가 (hb5u nginx)
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Private-Network "true";
→ 실패. <img> 태그는 no-cors 모드로 preflight를 보내지 않으며, Chrome은 사설 IP 자체를 차단하기 때문에 헤더로는 해결 불가.
2차 시도: OPTIONS preflight 204 처리 (hb5u nginx)
if ($request_method = 'OPTIONS') { return 204; }
→ 실패. 동일 이유. <img> 태그는 preflight 없이 직접 차단된다.
결론: DNS/프록시 레벨에서 images.hyperbook.com을 공인 IP로 노출하는 것이 유일한 해결책.
2. 설계 변경 — EC2 역방향 프록시
2.1 새 아키텍처
[브라우저]
│ HTTPS :443
▼
[AWS ALB/공인 IP → EC2 3.34.102.89]
│ EC2 nginx listen 8443 ssl
│ server_name images.hyperbook.com
│ proxy_pass https://100.125.27.70
▼
[hb5u 100.125.27.70 nginx :443]
│ root /home/moos/dev_ws/images
▼
[정적 파일 서빙]
2.2 변경 내역
| 항목 | 변경 전 | 변경 후 |
|---|---|---|
| DNS A 레코드 | 100.125.27.70 (Tailscale 사설 IP) | 3.34.102.89 (EC2 공인 IP) |
| SSL 종단 | hb5u (ZeroSSL 인증서) | EC2 (동일 인증서 복사) |
| 파일 저장 | hb5u /home/moos/dev_ws/images/ | 변경 없음 |
| 업로드 경로 | hb5u SSH/SCP | 변경 없음 |
| URL 구조 | https://images.hyperbook.com/\<ns>/파일명 | 변경 없음 |
핵심: 파일 저장·업로드 방식은 완전히 동일하다. 브라우저→서버 경로만 변경된다.
2.3 EC2 nginx 설정
server {
listen 8443 ssl;
server_name images.hyperbook.com;
ssl_certificate /etc/letsencrypt/live/images.hyperbook.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/images.hyperbook.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
location / {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin '*';
add_header Access-Control-Allow-Private-Network 'true';
add_header Access-Control-Allow-Methods 'GET, OPTIONS';
add_header Access-Control-Max-Age 1728000;
add_header Content-Length 0;
return 204;
}
proxy_pass https://100.125.27.70;
proxy_set_header Host images.hyperbook.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_ssl_verify off;
add_header Access-Control-Allow-Origin '*' always;
add_header Access-Control-Allow-Private-Network 'true' always;
add_header Access-Control-Allow-Methods 'GET, OPTIONS' always;
}
}
EC2 nginx 운영 주의사항:
- nginx.conf에 listen 443; (plain HTTP) 존재 — AWS ALB가 외부 443 → EC2로 포워딩
- HTTPS vhost는 반드시 listen 8443 ssl 사용 (listen 443 ssl 추가 시 bind 충돌 발생)
- systemctl restart nginx 금지 — nginx -s reload 또는 systemctl reload nginx 사용
3. 검증 결과
3.1 DNS 전파 확인
dig images.hyperbook.com +short → 3.34.102.89 (EC2)
3.2 HTTP 응답 확인
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Private-Network: true
Access-Control-Allow-Methods: GET, OPTIONS
3.3 OPTIONS preflight 확인
OPTIONS → 204 No Content
Access-Control-Allow-Private-Network: true
3.4 Codezy Chrome 렌더링 최종 검증 ✅
EC2 프록시 전환 후 Codezy(cmg-cv16)가 headless Chrome으로 thesis 문서 내 이미지 2건을 직접 검증했다.
| 파일 | 결과 | naturalWidth | naturalHeight |
|---|---|---|---|
| openwiki_grasp_frame_20260909.png | GET 200 image/png | 1440 | 1000 |
| grasp_hold_frame_20260909.png | GET 200 image/png | 1440 | 1000 |
| net::ERR_FAILED | 0건 | — | — |
부기 — cmg-cv16 로컬 DNS 캐시:
cmg-cv16의 로컬 resolver(getent)가 일시적으로 구 Tailscale IP(100.125.27.70)를 반환하는 상태였다. Codezy는 /etc/hosts에 3.34.102.89 images.hyperbook.com을 추가해 EC2 경로를 직접 지정하여 검증을 완료했다. 공개 DNS TTL 만료 후 cmg-cv16 로컬 캐시도 자동 해소된다. 별도 조치 불필요.
4. 영향 범위 및 주의사항
4.1 에이전트 업로드 경로 — 변경 없음
| 에이전트 | 업로드 방법 |
|---|---|
| EC2 상주 (EROS, Hermes) | scp 파일 moos@100.125.27.70:/home/moos/dev_ws/images/<ns>/ |
| hb5u 상주 (Geminy, Moojoco) | cp 파일 /home/moos/dev_ws/images/<ns>/ |
| 비거주 (Codezy 등) | roops-comm 요청 → EROS/Hermes 릴레이 |
4.2 SSL 인증서 만료 주의
hb5u의 ZeroSSL 인증서(/home/moos/dev_ws/dual_arms/certs/images/)를 EC2(/etc/letsencrypt/live/images.hyperbook.com/)로 복사했다.
만료일: 2026-10-18 — 갱신 시 EC2에도 재복사 필요.
4.3 hb5u 장애 시 영향
hb5u가 오프라인이 되면 EC2 프록시가 502/503을 반환한다. 이미지 서빙 불가. 현재 fallback 없음.
5. 사건 기록
이 설계 변경은 Codezy의 현장 발견에서 시작됐다. Codezy가 cmg-cv16에서 직접 이미지를 삽입하려다 Chrome PNA 차단을 발견했고, 단계적 디버깅(CORS 헤더 → preflight → DNS 프록시)을 통해 근본 원인을 규명했다. EROS가 EC2 인프라 작업을 수행하고, 사령관이 DNS 변경을 직접 적용했다.
v4 추가 (완전 종결): EC2 프록시 전환 후 Codezy가 headless Chrome으로 실제 렌더링을 검증해 완전 종결됐다. cmg-cv16 로컬 DNS 캐시로 인한 일시적 차단은 /etc/hosts 우회로 해소했으며, 공개 DNS TTL 만료로 자동 해소 예정이다.
교훈: Tailscale IP를 DNS에 직접 노출하는 구조는 브라우저 보안 정책 강화에 취약하다. 외부 공개가 필요한 서비스는 반드시 공인 IP를 경유해야 한다.
