Squid 학습 노트 — 프록시는 두 모드로 산다 (L0·L1)
이 글의 본체는 학습 노트 전문이다. 배치도·모드 비교 시퀀스·first-match 계단 다이어그램 3종, 실측 출력 9건, 운영 함정 5건, 확인 질문 6문(정답 접기)을 포함한다. 모든 출력은 Ubuntu 24.04 + Squid 6.14 컨테이너 실측값.
→ 전문 보기 (squid-study-l0-l1.html)
이 페이지는 핵심 요약본.
한 문단 요약
프록시는 두 모드로 산다. 대상이 http://면 Squid는 absolute-form 요청 라인을 끝까지 파싱하는 완전한 중간자다 — URL 수준 ACL·캐시가 가능하고, origin-form으로 재작성하며 Via/X-Forwarded-For를 붙여 오리진이 받는 바이트 자체가 달라진다. 대상이 https://면 클라이언트가 TLS 시작 전에 CONNECT host:443을 보내고, Squid는 ACL 판정·DNS resolve까지만 개입한 뒤 양방향 바이트 복사기가 된다(TLS는 클라이언트↔오리진 end-to-end — 증거는 curl에 오리진 인증서 CN=origin.lab이 그대로 보인다는 것). “아무것도 못 본다"가 아니라 개입이 시간 축으로 나뉜다는 것이 정확한 표현이다. 정책 쪽(L1)의 전부는 acl(어휘)과 http_access(문장)의 분리, 그리고 값 OR → 줄 AND → 줄간 first-match라는 3층 평가에 숨은 4번째 규칙 — 무매치 시 마지막 줄의 반대가 암묵 기본값 — 이다. deny all을 항상 명시하는 이유가 여기 있다.
구성
| 레벨 | 주제 | 핵심 |
|---|---|---|
| L0 | forward proxy와 CONNECT의 실체 | request-target 3형태, TCP 두 개 독립·DNS는 Squid의 몫, 두 동작 모드, 실측 4실험 |
| L1 | squid.conf 해부 — ACL과 first-match | 정의/사용 분리, 3층+암묵 기본값, dstdomain 선행 점·rDNS, 실습 5건 |
| QR | 치트시트 | 운영 반사신경 7건 |
L0 핵심 압축
- 왜 존재하는가 — 보안망은 직접 외부 연결을 금지하고 단일 관문만 연다. 관문이 하나로 모이면 정책(허용 도메인)·감사(접속 로그)·캐시가 따라온다. forward proxy = 클라이언트의 대리인, reverse proxy = 서버의 대리인 — nginx/Envoy로 익숙한 쪽은 후자, Squid의 본업은 전자.
- 프록시 요청은 별도 프로토콜이 아니다 — 그냥 HTTP이고, 다른 것은 request-target 형태 하나(RFC 9112):
| 형태 | 예시 | 언제 |
|---|---|---|
| origin-form | GET / HTTP/1.1 |
서버에 직접 (평소) |
| absolute-form | GET http://origin.lab/ HTTP/1.1 |
프록시에 http:// 요청 |
| authority-form | CONNECT origin.lab:443 HTTP/1.1 |
프록시에 터널 요청 |
HTTP_PROXY는 OS 기능이 아니라 라이브러리 관례 — curl·requests·Go는 읽고 Java는 기본 무시(-Dhttp.proxyHost).https_proxy는 “https:// URL에 쓸 프록시"라는 선택 기준이고, 프록시 자체와의 연결은 두 경우 다 보통 평문 http.- 배치: TCP 두 개, DNS는 Squid의 몫 — 클라이언트는 프록시 주소만 resolve한다. 목적지 이름은 요청 라인에 문자열로 실려 가고, resolve는 Squid가 한다. TCP #1(클라→프록시)과 #2(프록시→오리진)는 완전히 독립 — keep-alive 재사용도 타임아웃도 각자 간다.
실측 4실험이 증명한 것
| 실험 | 관찰 | 증명 |
|---|---|---|
| A | curl -v에 GET http://origin.lab:8080/ HTTP/1.1 |
목적지가 요청 라인 안에 (absolute-form) |
| B | 200 Connection established 후 TLS 시작, 인증서 CN=origin.lab |
TLS는 터널 “안에서”, end-to-end |
| C | tcpdump 오리진 도착분에 Via·X-Forwarded-For |
모드 1은 재작성 — 클라가 보낸 적 없는 헤더 추가 (내부 IP 노출) |
| D | 터널 12초 유지 중 로그 0줄 → 종료 후 1줄, elapsed=11970ms | CONNECT 로그는 종료 시점의 부검 기록. 바이트 필드 = Squid가 클라 방향으로 보낸 총량 |
L1 핵심 압축
- 정의와 사용의 분리 —
acl은 조건에 이름을 붙이는 정의,http_access는 이름을 조합한 판정 규칙. 같은 ACL을ssl_bump·cache·cache_peer_access등 다른 판정 지시자도 재사용한다. acl 정의 줄끼리는 순서 무의미,http_access줄들은 순서가 전부. - 평가 4규칙 — ① ACL 내부 값들은 OR ② 한 줄 위 ACL들은 AND(
!=부정) ③ 줄들 사이는 first-match 즉시 종결 ④ 무매치면 마지막 줄의 반대가 암묵 기본값. 운영 설정이 전부http_access deny all로 끝나는 이유 = ④에 보안을 맡기지 않기 위해서. - dstdomain 세부 —
.origin.lab(선행 점)은 자신+모든 서브도메인,origin.lab은 정확히 그 호스트만. URL에 IP가 오면 rDNS로 도메인화해 비교를 시도하는데, 실제 인터넷 PTR은 대개 클라우드/ISP 이름이라 매칭 실패 → 403. rDNS는 slow ACL이라 지연도 얹힌다.
실습 5건이 증명한 것
- dead rule 실측 — 광범위
allow아래의deny는 죽어 있다(차단 대상이 200). deny를 위로 올리면 403.TCP_DENIED+HIER_NONE(오리진에 아예 안 나감) = 403의 발원지는 Squid 정책이라는 판독 공식. - 암묵 기본값 실측 — allow가 하나도 없는 설정에서 200이 나온다. 마지막 줄이 deny라 기본값이 allow가 된 것 — deny 규칙으로만 채운 방화벽은 문이 열려 있는 상태.
- 문법 검증기 — 정의 안 된 ACL 참조는
FATAL: Bungled. 이 설정으로 restart하면 Squid가 아예 안 뜬다(reload는 기존 설정으로 버팀). 절차는 항상squid -k parse→ reload. - rDNS 도박 실측 — 같은 랩에서
127.0.0.2는 403인데127.0.0.1은 200 — /etc/hosts 덕에 rDNS가 origin.lab을 돌려줬기 때문. dstdomain의 IP 매칭은 내가 통제하지 않는 PTR 레코드에 의존하는 도박.
운영 함정 5건
| ID | 함정 | 증상 / 통제 |
|---|---|---|
| 0.A | NO_PROXY 해석은 구현마다 다르다 | 같은 NO_PROXY인데 특정 언어 앱만 내부 트래픽이 Squid로 흘러 403/지연 |
| 0.B | Via·X-Forwarded-For 정보 유출 | 내부 IP·프록시 호스트명이 외부 오리진에 전달. via off·forwarded_for delete |
| 0.C | 장수 터널은 로그에 안 보인다 | 살아있는 CONNECT는 로그 0줄 — “로그에 없다 = 트래픽 없다"는 오판. 장애 시 로그 폭주도 같은 원리 |
| 1.A | 죽은 규칙(dead rule) | “차단했는데 여전히 접속” = 위쪽 광범위 allow가 선점. 추가할 위치가 내용보다 어렵다 |
| 1.B | 앱이 IP로 호출 | rDNS가 기대 도메인을 안 주는 한 403. access.log URL 필드에 IP가 찍혀 있으면 이 케이스 |
치트시트
| 반사신경 | 내용 |
|---|---|
| 평가 3층 | 값 OR → 줄 AND(!=부정) → 줄간 first-match. 무매치면 마지막 줄의 반대 |
| 403 + HIER_NONE | 거부의 발원지는 오리진이 아니라 Squid 정책(ACL) |
| CONNECT 로그 | 터널 종료 시 기록. elapsed=터널 수명, 바이트=클라 방향 전달 총량 |
| dstdomain | .d.com=자신+서브도메인, d.com=정확히 그 호스트. IP 요청은 rDNS 의존 |
| 설정 변경 절차 | squid -k parse(문법) → squid -k reconfigure(reload). FATAL 설정으로 restart 금지 |
| 프록시 경유 테스트 | curl -v -x http://proxy:3128 URL — 요청 라인 형태(absolute/CONNECT)부터 본다 |
| env 변수 | 라이브러리 관례일 뿐(Java는 무시). https_proxy=https:// URL용 프록시. no_proxy 파싱은 구현차 |
확인 질문 (인출 연습)
원문에 L0 3문 + L1 3문이 정답 접기와 함께 있다 — 특히 L1의 3-ACL conf 판정 문제(줄 번호까지 답하기)는 first-match 체화용. 원문에서 풀기
관련 문서
- HTTP Tunneling — CONNECT 기초·Squid 실습 — L0의 CONNECT를 실습 중심으로
- HTTPS_PROXY 통신 흐름 실측 가이드 — CONNECT 이후 연결 재사용·5케이스 매트릭스
- TLS 1.3 over Squid tunnel — 터널 안 TLS의 실측
- Outbound 집약 설계서 — Squid 경로 일원화 — 이 노트의 L0가 그 설계서 Part 01–02의 배경에 대응