Files
ttipo 93147b88b7 feat(docker): 대용량 첨부 — ClamAV 검사기 · 공개 내려받기 호스트 · 받기 동시 연결 상한
- ClamAV 1.4.6 LTS 를 둘로 나눴다: clamd 는 백엔드와만 닿는 internal 네트워크(바깥 길 없음 · 서명 볼륨 읽기 전용),
  freshclam 은 자기 바깥 길에만. 비-root · cap_drop ALL. 운영은 늘 뜨고 개발은 profile clamav.
  clamd.conf 는 한 파일 한도 2047M 까지 끝까지 검사하고 암호 · 한도 초과를 판정으로 알린다(실측표는 mail/README).
  검사 중에 죽으면 남던 임시 파일을 띄울 때 지운다.
- nginx 를 포털 · 공개 내려받기(files.) 두 server 로 가른다(SHARE_PUBLIC_URL 과 CORS_ORIGIN 의 호스트 비교 — 같으면 포털이 함께 연다).
  조각 PUT 17m, 받기는 버퍼링 · 압축 끔 + X-Accel-Buffering 넘김 + 머리글 버퍼 16k,
  limit_conn 은 X-Forwarded-For 마지막 칸으로 IP 마다 공개 10 · 앱 안 30, 전체 공개 400 · 앱 안 200.
  받기 location 은 /api/ 블록 안의 대소문자를 가리지 않는 정규식이다(리뷰 — /FILES/ 같은 변형이 상한을 비켜 갔다).
  접근 로그는 경로 모양과 43자 토큰 모양으로 토큰을 가린다.
- 파일은 named volume <PROJECT_NAME>_files(개발도 — builder 단계가 /app/files 를 node 소유로 만든다), 백업하지 않는다.
- CI infra 잡: nginx 두 꼴 · 받기 경로 변형 시험(가짜 백엔드) · clamconf(-n 없이 기본값까지) · 틀린 SHARE_PUBLIC_URL 거부.
- mail/README: 악성 검사 · 공개 호스트 · NPM 칸(Advanced 다섯 줄) · 메모리 예산 · 문제 해결.

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

67 lines
6.0 KiB
Plaintext

# ==========================================
# clamd — 대용량 첨부(11단계)의 악성 검사기 · docker-compose.yml 의 clamav 가 읽는다
# (계약 docs/mail/api-contract.md 4절 "악성 검사", mail/README.md "악성 검사 (ClamAV)")
#
# 백엔드가 다 올린 파일을 TCP 3310 의 INSTREAM 으로 흘려보내고 판정 한 줄(\0 으로 끝)을 받는다(backend modules/file-storage/clamav-client.ts).
# 이 컨테이너는 백엔드와만 닿는 internal 네트워크(<PROJECT_NAME>_clamav)에만 붙는다 — 바깥 길이 없다.
# 서명은 clamav-updater(freshclam)가 받아 둔 볼륨을 읽기 전용으로 읽는다.
#
# 판정 — 2026-10-30 ClamAV 1.4.6 · 이 설정으로 확인 (mail/README.md "악성 검사 (ClamAV)" 의 표):
# stream: OK 깨끗함 — 2,047MB 짜리도 끝까지(코어 하나로 5분 안팎)
# stream: Eicar-Test-Signature FOUND 위험(EICAR — 압축 안에 있어도 같다). 그 밖의 서명 이름도 같은 꼴
# stream: Heuristics.Encrypted.Zip FOUND 검사하지 못함 — 암호 zip(ZipCrypto · AES). 7z 는 .7Zip, 사용자 암호 pdf 는 .PDF
# stream: Heuristics.Limits.Exceeded.<한도> FOUND 검사하지 못함 — MaxFileSize · MaxRecursion · MaxFiles · MaxScanTime 등 아래 한도
# INSTREAM size limit exceeded. ERROR StreamMaxLength 를 넘는 스트림 — 답한 뒤 연결을 닫는다(FIN, 계속 보내면 RST)
# 같은 압축 안에 진짜 서명과 휴리스틱(암호 · 한도)이 함께 있으면 진짜 서명이 나온다(HeuristicScanPrecedence 기본값 끔)
#
# 이미지의 기본 설정을 이 파일로 통째로 바꾼다. compose 가 처음부터 비-root(이미지의 clamav 사용자 100:101)로 띄우므로
# 이 파일은 그 사용자가 읽을 수 있어야 한다(644). 바꾼 뒤에는 docker compose … up -d --force-recreate clamav
# CI(infra 잡)가 clamconf 로 이 파일을 읽고 아래 한도 · 경보 값이 그대로인지 본다
# ==========================================
DatabaseDirectory /var/lib/clamav
# INSTREAM 으로 받은 내용 · 압축을 푼 내용을 여기에 잠시 쓰고 검사가 끝나면 지운다 — 컨테이너 쓰기 계층(도커 data-root 의 디스크)이다.
# 한 번에 검사하는 수(백엔드가 2개) × (파일 2GB + 푼 내용 MaxScanSize 까지)만큼 든다. tmpfs 로 바꾸지 않는다 — 메모리 한도에 잡힌다
TemporaryDirectory /tmp
# 로그는 표준 출력으로만(docker logs) — 파일에 남기지 않는다. 깨끗한 파일은 남기지 않고(LogClean 기본값 끔),
# 판정은 "instream(<백엔드 주소>@<포트>): <이름> FOUND" — 파일 이름은 남지 않는다
Foreground yes
LogTime yes
# 받는 곳 — 컨테이너의 3310. 닿는 것은 백엔드뿐이다(internal 네트워크 · 호스트 포트 없음). 유닉스 소켓은 쓰지 않는다
TCPSocket 3310
# ------------------------------------------
# 한도 — 한 파일 한도(MAIL_LARGE_FILE_MAX_SIZE_MB, 최대 2,047MB = 2,146,435,072바이트)까지 끝까지 검사한다
# ------------------------------------------
# INSTREAM 으로 받는 양 — 이 값까지는 받는다(같으면 받는다 · 1바이트 넘으면 "INSTREAM size limit exceeded. ERROR").
# 한 파일 한도의 최대값과 같다 — 백엔드는 이보다 큰 파일을 만들지 않는다(env 는 2,047 을 넘지 못한다). 넘으면 설정 오류다
StreamMaxLength 2047M
# 한 파일(밖의 파일 · 압축 안의 파일)의 크기 — 넘는 것은 검사하지 않는다. ClamAV 는 2GiB − 1 바이트 넘게는 검사하지 못한다(1.4.6 은 그 위를 2,147,483,645 로 줄인다)
MaxFileSize 2047M
# 파일 하나에서 검사하는 양의 합 — 압축 · 문서 안을 풀어 검사하는 양까지 센다. 한 파일 한도의 두 배 — 풀면 커지는 압축도 대부분 끝까지 본다
MaxScanSize 4094M
# 압축(ZIP) 하나를 검사하는 시간 — 1.4 에서는 ZIP 에만 걸린다(풀면 수 GB 인 zip 은 이 시간에 "Heuristics.Limits.Exceeded.MaxScanTime").
# 백엔드가 다 보낸 뒤 판정을 기다리는 시간(30분 + 보낸 1MB 마다 1초 — 2,047MB 면 약 64분, backend file-storage.constants.ts 의 clamavVerdictTimeoutMs)보다
# 짧게 둔다 — 넘으면 백엔드가 끊고 미뤄서 처음부터 다시 검사한다. zip 이 아닌 압축(7z · tar.gz 등)에는 시간 상한이 없다(MaxScanSize 까지 푼다 —
# 4,094MB 를 코어 하나로 10분 안팎, 백엔드의 30분 몫이 이것을 덮는다)
MaxScanTime 300000
# MaxRecursion(압축 겹 17) · MaxFiles(압축 안 파일 10,000) 등 나머지 한도는 기본값
# 한도를 넘거나(Heuristics.Limits.Exceeded.*) 암호가 걸려(Heuristics.Encrypted.* — zip · 7z · rar · pdf) 끝까지 보지 못한 파일을 판정으로 알린다.
# ⚠️ 끄면 그런 파일이 "깨끗함(stream: OK)"으로 온다(확인함) — 백엔드는 이 두 이름을 "검사하지 못함"으로 가른다(받을 수는 있다)
AlertExceedsMax yes
AlertEncrypted yes
# ------------------------------------------
# 메모리 — 서명을 읽으면 1GB 안팎(2026-10 서명 363만 개). 서명을 다시 읽을 때(아래 SelfCheck) 두 벌을 들지 않는다 —
# 옛 서명을 버리고 새로 읽는 30초 안팎 동안 검사 · PING 이 기다린다(백엔드의 검사 차례는 그동안 쌓였다가 이어진다).
# 켜 두면(기본값) 새 서명을 다 읽을 때까지 두 벌을 든다(ClamAV 문서 — 2GB 남짓, 한도 2g 를 넘는다). 메모리 예산은 mail/README.md "메모리 예산"
# ------------------------------------------
ConcurrentDatabaseReload no
# 서명이 바뀌었는지 10분마다 본다 — 갱신(clamav-updater)이 알려 주지 않으므로(네트워크로 잇지 않는다) 이것이 새 서명을 읽는 길이다
SelfCheck 600
# libclamav 가 메모리를 얻지 못하면 끝낸다 — 재시작 정책으로 다시 뜬다(어정쩡한 상태로 남지 않게)
ExitOnOOM yes