저장소에 파일 이름이 퍼센트 인코딩 문자열 그대로 들어가 있었다.
%EC%83%81%ED%92%88%EC%9D%B4%EB%AF%B8%EC%A7%801.jpg
Table of contents
Open Table of contents
증상 — 리터럴로 들어간 키
한글 파일명이 UTF-8 바이트를 감싼 형태로 리터럴 저장돼 있었다.
원래 한글이었어야 할 이름이 % 로 감싼 문자열 자체로 키가 된 상태였다. 이 상태면 정상적인 URL 요청으로는 그 파일에 못 닿을 것으로 보였다.
검증 — 세 형태로 받아 봤다
curl 로 세 가지 형태를 다 돌려 봤다.
curl -I 'https://.../%EC%83%81%ED%92%88%EC%9D%B4%EB%AF%B8%EC%A7%801.jpg'
403
%EC 로 시작하는 그대로 보내면 거부됐다.
curl -I 'https://.../%25EC%2583%2581%25ED%2592%2588%25EC%259D%25B4%25EB%25AF%25B8%25EC%25A7%25801.jpg'
200
%25EC 로 한 겹 더 씌우니 열렸다.
curl -I 'https://.../상품이미지1.jpg'
403
원문 한글도 거부됐다.
하나만 돌렸으면 403 을 보고 깨졌다로 끝냈을 것이다. 세 개를 대조하니 어느 형태가 맞는지가 그 자리에서 나왔다.
원인 — 이중 인코딩이라야 닿는 키
저장소가 요청 경로를 한 번 디코드해서 키와 맞춘다.
요청 %25EB...
↓ 저장소가 1회 디코드
키 %EB... ← 실제 저장된 이름
키 자체가 이미 %EC 형태이니 보낼 때 한 번 더 감싸야 그 값이 나온다.
이론상으로는 일반적인 요청이 전부 실패해야 하는 상황이었다. 그런데 운영에서는 이미지가 잘 나오고 있었다.
결과 — 클라이언트가 인코딩했다
소비하는 클라이언트가 URL 경로를 자동으로 한 번 인코드하고 있었다.
DB 값 %EB...
↓ 클라이언트가 1회 인코드
요청 %25EB...
↓ 저장소가 1회 디코드
키 %EB... ← 맞음
인코드와 디코드가 상쇄되어 항등이 된다.
그러면 인코딩 형태와 무관하게 DB 값과 실제 키가 같기만 하면 성공한다. 형태가 이상한 것과 동작이 깨진 것은 별개였다.
조치 — 저장소를 건드리지 않기
이것을 알기 전과 후가 달랐다.
[전] 저장소 객체 수만 건을 한글로 rename + DB도 수정
[후] DB를 실제 키에 맞추기만 하면 됨
저장소 객체 수만 건의 rename 이 없어졌다.
앞의 것은 위험하고 오래 걸리는 작업이고 뒤의 것은 조회와 정합 확인으로 끝난다.
주의 — 기대고 있는 조건
다만 이 성립이 클라이언트 쪽 조건 하나에 기대고 있었다.
[웹 클라이언트] 자동 인코드 함 → 됨
[명령줄 도구] 안 함 → 깨짐
클라이언트가 유효한 퍼센트 문자를 한 번 더 인코드해 준다는 조건이다.
curl 처럼 직접 접근하는 경로에서는 그대로 깨진다. 그래서 지금 정상인 것과 구조가 건전한 것을 갈라서 적었다.
근본 정상화는 별도 과제로 남겼다.
저장소 수만 건 rename
+ 전 소비처 호환 재검증
이번 작업으로 묶기에는 너무 크고 당장 필요한 것은 DB 정합이며 그것만으로 운영이 돈다.
검증 도구로 403 이 나와도 실패로 단정하지 않는 습관도 남았다. 그 도구가 자동 인코드를 하는지에 따라 결과가 달라지므로 한 번의 실패는 원인을 가르지 못한다.
정리
- 저장소가 요청 경로를 한 번 디코드하면 리터럴 키는 이중 인코딩이라야 닿는다
- 운영이 정상인 이유가 클라이언트의 자동 인코드일 수 있다
- 그러면 형태와 무관하게
DB값과 실제 키가 같으면 성공한다 - 형태가 이상한 것과 동작이 깨진 것은 별개다
- 조치가 저장소 변경에서
DB정합으로 줄어든다 - 자동 인코드를 안 하는 도구에서는 깨진다
- 근본 정상화와 당장의 정합을 구분한다
- 검증 도구로 거부가 나와도 그 도구의 동작을 먼저 따진다
- 한 번의 실패는 원인을 안 가르므로 여러 형태로 대조한다