TLS 가 앞단에서 종단되고 앱에는 HTTP 로 전달되는 구성에서 X-Forwarded-* 가 전부 무시되어 화면 백지·IP 왜곡·webhook 403 이 함께 발생했다. 코어에 신뢰 프록시 설정 지점이 아예 없던 것이 원인이다. - config/trustedproxy.php 로 내장 TrustProxies 미들웨어에 값을 공급한다. bootstrap/app.php 는 건드리지 않는다 — withMiddleware 클로저는 .env 로드 전에 평가되어 env 가 항상 null 이 되는 조용한 no-op 이다. - 판정은 App\Support\TrustedProxyDiagnostic 단일 SSoT 에서 계산하고 대시보드 알림·환경설정 고급 탭·설치 마법사·trusted-proxy:status 네 면이 소비한다. 판정식은 "HTTPS 인식 실패" 가 아니라 "X-Forwarded-* 수신 중 AND 신뢰 프록시 미설정" 이다 — HTTP 전용 사이트가 프록시 뒤에 있으면 화면은 정상인 채로 나머지만 조용히 어긋나기 때문이다. - 값 편집 UI 와 쓰기 엔드포인트는 두지 않는다(잠금 역설 + XFF 위조 경로). - 혼합 콘텐츠 차단을 부트스트랩 폴백의 별도 사유로 가른다. 새로고침으로 낫지 않으므로 버튼을 렌더하지 않고, 원인·조치는 콘솔로 운영자에게 보낸다. - 대시보드 알림을 심각도로 배치한다 — warning 은 상단 배너, 그 외는 하단 카드. 같은 알림이 두 곳에 뜨지 않으며, 여러 건은 간격을 두고 쌓인다. 공개 이슈: (@lyg-kaban 제보)
96 lines
6.0 KiB
YAML
96 lines
6.0 KiB
YAML
# audit:allow test-scenario-coverage reason: 본 매니페스트는 신규 기능 시나리오 매트릭스 SSoT 로 기록. 축 곱(6×3×3×3×2×2 = 648)이 커 전 조합 docblock 매핑은 비현실적이며, 핵심 회귀 가드는 test_files 의 통과 테스트로 커버.
|
||
|
||
feature: 리버스 프록시 신뢰 설정 (Trusted Proxy)
|
||
|
||
description: |
|
||
TLS 가 앞단에서 종단되고 앱에는 HTTP 로 전달되는 구성(AWS ALB, CloudFront, Cloudflare,
|
||
nginx/Apache 리버스 프록시, ngrok)에서 요청 객체가 접속 주소와 방문자 IP 를 올바르게
|
||
인식하도록 신뢰할 프록시를 지정한다 (공개 #124).
|
||
|
||
배경:
|
||
- 코어에 신뢰 프록시 설정이 전혀 없어, Laravel 내장 TrustProxies 미들웨어가
|
||
X-Forwarded-* 를 전부 무시했다. 그 결과 요청이 평문 HTTP 로 인식되어
|
||
asset()/url() 이 http:// 절대 URL 을 만들고, HTTPS 페이지에서 혼합 콘텐츠로
|
||
차단되어 사이트 전체가 백지가 되었다.
|
||
- 같은 원인에서 통보 IP 화이트리스트 403, 로그인 시도 제한 붕괴, IP 기록 왜곡,
|
||
쿠키 Secure 누락, SEO canonical 다운그레이드, GeoIP 오판정이 함께 갈라진다.
|
||
|
||
조치:
|
||
- config/trustedproxy.php 1개 (`'proxies' => env('TRUSTED_PROXIES')`).
|
||
코어 PHP 코드 수정 0줄 — 내장 미들웨어가 요청 시점에 이 키를 폴백으로 읽는다.
|
||
- bootstrap/app.php 는 건드리지 않는다. withMiddleware 클로저는 .env 로드 전에
|
||
평가되어 env() 가 항상 null 이므로 그 경로는 조용한 no-op 이 된다.
|
||
- 미설정이 기본값이므로 기존 설치처 동작은 바뀌지 않는다(opt-in).
|
||
|
||
진단 (읽기 전용, 4면):
|
||
- 판정식은 "HTTPS 인식 실패" 가 아니라
|
||
`X-Forwarded-* 수신 중 AND 신뢰 프록시 미설정` 이다. HTTP 전용 사이트가 프록시
|
||
뒤에 있으면 화면은 완전히 정상 렌더되면서 나머지 파손만 조용히 계속되기 때문이다.
|
||
- 판정은 App\Support\TrustedProxyDiagnostic 한 곳에서만 계산하고
|
||
대시보드 알림 · 환경설정 고급 탭 · 설치 마법사 · Artisan 커맨드가 소비한다.
|
||
- 값 편집 UI 는 두지 않는다 — 잠금 역설(프록시 뒤에서는 그 화면 자체가 뜨지 않는다)
|
||
과 권한 승격(관리자 계정 탈취가 곧 XFF 위조 경로)이 이유다.
|
||
|
||
axes:
|
||
proxies_setting: [unset, star, double_star, single_ip_match, single_ip_mismatch, cidr]
|
||
forwarded_proto: [https, http, absent]
|
||
forwarded_for: [single, chain, absent]
|
||
forwarded_host: [same, different, absent]
|
||
forwarded_port: [nonstandard, absent]
|
||
session_secure_cookie: [unset, true]
|
||
|
||
exclusions:
|
||
- { proxies_setting: unset, forwarded_proto: https, reason: "미설정은 헤더를 전부 무시 — 헤더 조합이 결과를 바꾸지 않는다" }
|
||
- { proxies_setting: unset, forwarded_proto: http, reason: "동일 사유" }
|
||
- { proxies_setting: unset, forwarded_for: chain, reason: "동일 사유" }
|
||
- { proxies_setting: unset, forwarded_host: different, reason: "동일 사유" }
|
||
- { proxies_setting: unset, forwarded_port: nonstandard, reason: "동일 사유" }
|
||
- { proxies_setting: single_ip_mismatch, forwarded_proto: https, reason: "목록 불일치는 미설정과 동일 경로 — 헤더 조합이 결과를 바꾸지 않는다" }
|
||
- { proxies_setting: single_ip_mismatch, forwarded_for: chain, reason: "동일 사유" }
|
||
- { proxies_setting: single_ip_mismatch, forwarded_host: different, reason: "동일 사유" }
|
||
- { forwarded_proto: absent, session_secure_cookie: unset, reason: "스킴 헤더가 없으면 쿠키 Secure 자동 판정 축이 성립하지 않는다" }
|
||
- { session_secure_cookie: true, forwarded_proto: http, reason: "명시 설정은 스킴 판정을 우회하므로 조합이 무의미하다" }
|
||
- { session_secure_cookie: true, forwarded_proto: absent, reason: "동일 사유" }
|
||
|
||
effects:
|
||
- unset_ignores_forwarded_headers
|
||
- star_restores_scheme_ip_and_root
|
||
- double_star_behaves_like_star
|
||
- chain_requires_listing_every_hop
|
||
- ip_list_match_trusts_and_mismatch_rejects
|
||
- cidr_notation_is_honored
|
||
- config_file_exposes_proxies_key_from_env
|
||
- bootstrap_does_not_reintroduce_env_trust_proxies
|
||
- env_examples_document_trusted_proxies
|
||
- installer_header_list_matches_core_diagnostic
|
||
- cookie_secure_follows_trusted_scheme
|
||
- direct_exposure_is_not_a_warning
|
||
- forwarded_without_config_is_warning
|
||
- http_only_behind_proxy_still_warns
|
||
- configured_proxy_is_ok
|
||
- empty_string_counts_as_unset
|
||
- console_context_is_not_applicable
|
||
- dashboard_alert_only_on_warning_with_warning_type
|
||
- diagnostic_endpoint_is_read_only
|
||
|
||
test_files:
|
||
- tests/Feature/Http/TrustedProxyContractTest.php
|
||
- tests/Feature/Http/TrustedProxyDiagnosticTest.php
|
||
|
||
notes: |
|
||
`**` 는 Laravel 내장 TrustProxies 에서 `*` 과 **같은 코드 경로**를 탄다
|
||
(`setTrustedProxyIpAddressesToTheCallingIp`). 별도 패키지 시절의 "모든 프록시 신뢰"
|
||
의미가 아니므로, 다단계 체인에서는 둘 다 마지막 프록시를 방문자로 본다. 실측으로
|
||
확인했고 `double_star_behaves_like_star` 가 그 사실을 계약으로 잠근다 — 프레임워크
|
||
업그레이드로 의미가 갈라지면 문서·설정 주석과 함께 갱신해야 한다.
|
||
|
||
프록시를 실제로 경유하는 브라우저 축(혼합 콘텐츠 차단 → 화면 백지)은 이 매니페스트
|
||
범위 밖이다. Playwright 에 TLS 종단 프록시 픽스처를 두면 인증서·포트·globalSetup 이
|
||
기존 E2E 의 단일 origin 전제를 깨뜨리므로, 그 축은 Chrome MCP 수동 매트릭스가 덮는다.
|
||
부팅 안내의 `blocked` 분기는 프록시 없이 재현 가능하므로
|
||
`tests/scenarios/bootstrap-parse-failure.yaml` 이 별도로 다룬다.
|
||
|
||
이미 발송된 서명 URL 무효화 · 과거 적재 IP · SEO 캐시/사이트맵 재생성은 설정을 켜도
|
||
자동으로 낫지 않는 1회성 후속 조치다. 코드가 아니라 운영 절차이므로 축에 넣지 않고
|
||
docs/backend/reverse-proxy.md 가 문서로 고정한다.
|