채팅 섬네일이 s3_cdn_url을 우회하고 S3 버킷 원본 URL을 사용함

최근 Cloudflare R2 업로드 버킷 구성을 했는데 채팅 썸네일 이미지가 깨졌습니다. 그래서 조사를 해보니 제 구성에 대한 빠른 해결책을 찾았고, 이 게시글을 발견했습니다: Cloudflare R2 Image URL Display Issue: Detailed Explanation and Fix. 어쨌든 다른 S3 업로드 버킷 구성을 살펴보니 이 버그가 Cloudflare에 특화된 문제가 아니라는 것을 알게 되었습니다.


설명

외부 S3 또는 호환되는 오브젝트 스토리지가 업로드로 구성되었을 때, 채팅 썸네일 이미지는 CDN을 우회하고 버킷 URL에서 직접 로드됩니다.

보안 강화된 외부 S3 호환 버킷(예: Cloudflare R2)의 경우, 채팅 썸네일이 깨져서 표시되지 않습니다.

근본적인 문제는 채팅 직렬화기(serializer)가 썸네일에 s3_cdn_url 설정을 적용하는 데 실패한다는 것입니다. 구성된 CDN을 통해 이미지를 라우팅하는 대신, 브라우저 페이로드에 원본 내부 S3 버킷 URL이 그대로 노출되고 있습니다.

재현 방법

이 문제는 Meta 및 S3 업로드 버킷을 사용하는 다른 사이트에서도 재현됩니다:

  1. 채팅이나 채널에 이미지를 게시합니다.
  2. 콘솔에서 썸네일 이미지 URL을 검사합니다.
  3. 이미지를 클릭하여 더 큰 원본 이미지를 열고 URL을 검사합니다.
  4. 썸네일 URL과 비교합니다.

아래는 Meta 채팅의 예시입니다.

썸네일 URL: 버킷에서 직접

https://cdck-file-uploads-global.s3.dualstack.us-west-2.amazonaws.com/meta/optimized/4X/4/7/9/479815360e0e6e0cd9f4ba565891776e84aea532_2_375x500.jpeg

원본 URL: CDN을 통해

https://global.discourse-cdn.com/meta/original/4X/4/7/9/479815360e0e6e0cd9f4ba565891776e84aea532.jpeg

콘솔에서 썸네일의 <img... HTML에는 data-large-src = CloudFront CDN URL, src = AWS 버킷 URL이 포함되어 있습니다.

스크린샷

영향:

  • 기본적으로 보안이 강화되어 원본 버킷 엔드포인트에 대한 인증되지 않은 액세스를 차단하는 Cloudflare R2와 같은 S3 호환 스토리지의 경우, 채팅 썸네일 이미지(최적화)가 깨집니다.
  • 원본 버킷 엔드포인트에 대한 액세스를 허용하는 AWS 및 기타 S3 호환 오브젝트 스토리지 버킷의 경우, 채팅이 CDN을 완전히 우회하므로 대역폭이 누출됩니다. 이로 인해 모든 채팅 썸네일 트래픽에 대해 직접적인 S3 아웃바운드(equivalent: egress) 수수료가 부과됩니다.
  • 인프라 정보 누출: 원본 백엔드 스토리지 URL(내부 버킷 이름 및 때로는 계정 ID 포함)이 클라이언트 JSON 페이로드에 노출되고 있습니다.

PR:

이 문제를 수정하기 위한 PR을 여기에 올렸습니다:

Sam이 채팅 작성기(composer) 미리보기에 getURLWithCDN을 추가한 것으로 보이지만, 채팅 스트림까지 반영되지 않는 것 같습니다.

작성기 수정이 프로토콜 불일치(//https://)로 인해 getURLWithCDN이 충돌하는 일부 S3 구성에서도 실패했을 수 있다고 생각합니다. 어쨌든 위의 PR은 Sam의 작업을 확장하여 스트림에 래퍼를 추가하고 프로토콜 비의존적(agnostic)으로 만드는 것입니다.

임시 우회 방법:

이것이 Cloudflare만의 문제가 아니라 더 큰 문제임을 깨닫기 전에, 가벼운 테마 컴포넌트를 만들었습니다. 이는 브라우저가 다운로드를 시도하기 전에 Chat DOM의 원본 S3 도메인을 가로채 적절한 CDN 도메인으로 교체합니다. 이를 통해 트래픽이 올바르게 라우팅되고 대역폭 누출을 막을 수 있습니다. 이를 S3 호환 오브젝트 스토리지 모두에서 작동하도록 조정했습니다. 설정은 두 가지뿐입니다 - 원본 S3 버킷 URLS3 CDN URL.

(여기서 GitHub 원박스가 왜 깨지는지 모르겠습니다) 이제 수정되었습니다.

5개의 좋아요

아마 사이트 이전 때문일 것 같네요… 방금 다시 처리했습니다.

2개의 좋아요

안녕하세요, PR이 이미 병합되었나요?

감사합니다

2개의 좋아요

내 친구 discourse-triage-bot이 몇 가지 테스트를 수정한 후, 이제 인간 팀원의 검토만 기다리고 있습니다 :slight_smile:

5개의 좋아요

지금 병합된 것 같습니다.

4개의 좋아요

작성자 요청으로 열림

커스텀 이모지가 추가된 사이트의 경우, 해당 수정 사항이 병합되면서 이제 채팅에서 이모지가 깨지는 문제가 발생합니다.

표준 게시물 이미지(이것은 rake posts:rebake로 수정 가능)와 달리, 채팅용 커스텀 이모지는 /site.json을 통해 프론트엔드에 동적으로 전달됩니다.

데이터베이스에 프로토콜이 누락된 S3 URL(예: //bucket.endpoint...)이 포함되어 있거나, app.yml 환경 변수와 완벽하게 일치하지 않는 가상 호스팅 스타일 도메인을 사용하는 경우, Discourse의 내부 CDN replacer가 조용히 실패합니다. 그러면 브라우저로 원본 버킷 URL이 그대로 전달되어 채팅의 커스텀 이모지가 깨집니다.

수정 방법:

이를 영구적으로 수정하려면 데이터베이스에서 원본 버킷 URL을 CDN URL로 강제 재매핑한 뒤, /site.json이 재생성되도록 사이트 캐시를 비워야 합니다.

1. 컨테이너 진입:

서버에 SSH로 접속하여 Discourse 컨테이너(보통은 app, 두 컨테이너 구성인 경우 web_only)에 진입합니다.

cd /var/discourse
./launcher enter app

2. URL 재매핑:

내장된 Discourse remap 도구를 실행합니다. 마이그레이션 스크립트가 남겨놓는 https:// 변형과 스키마 없는 // 변형 모두를 포착하기 위해 두 번 실행해야 합니다.

플레이스홀더를 실제 원본 버킷 URL과 실제 CDN URL로 교체하세요:

# 표준 https:// URL 수정
discourse remap "https://<your-bucket>.<your-endpoint>.com" "https://cdn.your-domain.com"

# 스키마 없는 // URL 수정 (보통 커스텀 이모지가 깨지는 원인)
discourse remap "//<your-bucket>.<your-endpoint>.com" "https://cdn.your-domain.com"

3. 캐시 비우기

/site.json은 캐싱이 심하게 이루어지므로, 포럼이 새로운 URL을 제공하도록 rails 캐시를 비워야 합니다:

rails 콘솔을 엽니다:

rails c

다음 명령을 실행합니다:

Rails.cache.clear
Site.clear_cache
exit

4. 새로고침

브라우저를 하드 새로고침하고(아직 테마 컴포넌트 우회 조치를 사용하고 있다면 이를 비활성화합니다). 이제 채팅의 커스텀 이모지가 수정되어 CDN을 통해 정상적으로 로드되어야 합니다.

3개의 좋아요

릴리, 정말 잘해줘서 고마워. 두 번의 리맵 이후로 이제 내 오래된 업로드 파일과 섬네일이 예상대로 정상 작동해. 그 전에는 “/admin/config/customize/themes” 이미지도 제대로 안 나왔거든. 지금은 다 해결됐어. 멋지다.

고마워

1개의 좋아요