Files
arasystem/frontend/nginx-internal-api-closed.conf
ttipo d1ef0da488 feat(docker): 12단계 계정 보안 — 엔진 웹훅 · /api/internal 닫기 · 요청 대상 400
메일 프로그램(IMAP · SMTP) 로그인 기록을 엔진 웹훅으로 받는다.
- 루트 env MAIL_WEBHOOK_SECRET(setup · backend 공용, 운영 필수)과 backend 의 APP_NETWORK_SUBNET
- setup 이 엔진 웹훅을 맞추고(표시 머리글 — 소금 + 키 지문) 서명한 빈 묶음으로 받는 곳을 확인한다
  키를 바꾼 실행 · 게이트웨이 OTP · 테스트 직원의 402 를 따로 다룬다
- nginx 두 server 가 /api/internal/ 을 닫고, 요청 대상의 날 \(경로) · # · 0x80 이상은 location 전에 400
  Express 가 이런 대상을 다시 읽어 닫은 경로(/api/internal · /api/health)가 백엔드에서 열렸다(2026-10-31 리뷰)
- CI "닫는 경로 시험"(두 server × 세 경로의 변형) · 지시어만 세는 정적 검사
- mail/README: 웹훅 · 12단계 전부터 운영 중인 서버를 올리는 차례 · 휴대폰을 잃은 직원(관리 API 명령)
엔진 DNS 가 Custom(운영)이어도 웹훅은 backend 를 푼다 — 메일 컨테이너가 도커 DNS 를 쓴다(개발에서 확인).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:45:23 +09:00

22 lines
2.0 KiB
Plaintext

# ==========================================
# 엔진만 부르는 백엔드 경로 /api/internal/ 를 바깥에 열지 않는다 (12단계 — docs/mail/api-contract.md 5절 "엔진 웹훅 받기")
#
# 두 server(포털 · 공개 내려받기)의 location ^~ /api/ 블록 **안에** 넣는다(nginx.conf). 템플릿이 아니다(/etc/nginx/snippets/internal-api-closed.conf).
# 엔진 웹훅(POST /api/internal/mail-engine-events)은 메일 엔진이 app-network 로 backend 에 바로 붙어 부른다 — 이 nginx 를 거치지 않는다.
# 바깥에서 온 요청은 없는 경로와 같은 404 RES_001(JSON — @api_not_found)로 끝내고 백엔드에 넘기지 않는다
# (세 겹의 첫째 — 백엔드는 접속 주소가 앱 네트워크인지와 본문의 서명을 본다). 본문 한도(1m)를 넘는 요청도 JSON 413 FILE_002 다.
# 닫는 꼴 (CI 의 infra 잡 "닫는 경로 시험"이 두 server 에서 실제 요청으로 본다):
# - 대소문자를 가리지 않는다(~*) — /api/Internal/… 도 닫는다
# - nginx 는 %XX 를 풀고 겹 / · . · .. 를 정리한 뒤 location 을 고르므로 /api//internal/… · /api/%69nternal/… · /api%2Finternal/… · /api/x/../internal/… 도 걸린다
# - [/\x5c] — %5C(인코딩한 역슬래시 — nginx 가 \ 로 풀어 고른다)도 닫는다
# - 날 \(경로) · 날 # · 0x80 이상 바이트가 든 원문 대상은 여기까지 오지 않는다 — server 단계에서 400 VAL_001 이다(nginx.conf 의 $request_target_rejected).
# nginx 는 그 꼴을 이 정규식에 맞추지 못하는데 백엔드(Express — parseurl 의 url.parse 되읽기)는 \ 를 / 로 바꿔 받는다(2026-10-31 리뷰)
# /api/ 블록의 다른 정규식 location 보다 먼저 넣는다 — 정규식은 적은 차례로 처음 맞은 것을 쓴다
# ==========================================
location ~* ^/api/internal(?:[/\x5c]|$) {
error_page 404 @api_not_found;
error_page 413 @api_payload_too_large;
return 404;
}