NextPDF Gotenberg 보안 및 운영
한눈에 보기
섹션 제목: “한눈에 보기”이 브리지는 애플리케이션이 보유한 문서를 네트워크를 통해 외부 서비스로 전송합니다. 따라서 서버 측 요청 표면이자 전송 보안 표면을 형성합니다. 이 패키지는 두 표면 모두에서 구체적이고 검증 가능한 제어를 구현합니다. 그렇다고 해서 시스템이 그 자체로 안전해지지는 않습니다. 이 제어는 Gotenberg 서비스를 배포하고 운영하는 방식과 결합될 때에만 효과를 발휘합니다. 이 페이지는 구현된 제어와 이를 완성하는 운영상의 책무를 설명합니다.
여기서 어떤 제어도 보장을 의미하지 않습니다. 각 제어는 명시된 한계를 가진, 정의되고 테스트로 검증된 동작입니다.
두 가지 정책 계층
섹션 제목: “두 가지 정책 계층”브리지에는 두 가지 별개의 보안 정책이 있으며, 이들은 서로 다른 계층에서 작동합니다.
- 전송 정책 (
GotenbergSecurityPolicy) — URL 스킴 적용, 서버 측 요청 위조(SSRF) 차단, DNS 리바인드 방어, 입력 크기 제한, 파일 이름 차단을 담당합니다. 아래에서 자세히 설명하는 계층입니다. - HTML 파싱 정책 — 파싱 계층의 콘텐츠 정책입니다. 기본값은 NextPDF 코어 기본 정책이며, 콘텐츠가 렌더러에 도달하기 전에 적용됩니다. 이는 전송 정책을 보완하지만 전송 정책과는 독립적입니다. 이 페이지는 전송 정책을 다룹니다.
서버 측 요청 위조(SSRF) 차단
섹션 제목: “서버 측 요청 위조(SSRF) 차단”구성된 API URL은 어떤 바이트도 프로세스를 떠나기 전에 차단 검사를 거칩니다. 이 제어는 세 부분으로 구성됩니다.
스킴 적용. https만 허용됩니다(대소문자 구분 없음). 일반 http:// URL은 거부됩니다. 따라서 모든 변환과 상태 검사 프로브에는 전송 계층 보안(TLS)이 필수입니다.
주소 차단. 호스트가 IP 리터럴인 경우 사설 또는 예약된 범위에 속하면 거부됩니다. 호스트가 이름인 경우 해당 이름의 모든 A 및 AAAA 레코드를 해석하며, 해석된 주소 중 하나라도 사설 또는 예약 주소이면 요청이 거부됩니다. 단일 주소가 아니라 전체 레코드 집합을 해석하는 것은 공개 주소도 함께 반환하는 이름 뒤에 사설 주소를 숨기는 공격자를 무력화하기 위한 제어입니다. 이는 OWASP SSRF 방지 지침에서 도메인 이름 뒤의 모든 IP 주소(IPv4 및 IPv6용 A 및 AAAA 레코드)를 가져와 각각을 허용 목록과 대조하여 검증하는 것으로 설명하는 접근 방식입니다(OWASP Cheat Sheet Series, SSRF 방지, 애플리케이션 계층 방어; 이 페이지의 RAG 사이드카에 고정됨).
검사 시점/사용 시점(TOCTOU) 재검사. 검증 중에 캡처된 주소 집합은 요청 직전에 다시 해석되어 비교됩니다. 새로운 주소가 나타나면 요청은 DNS 리바인딩 오류와 함께 중단됩니다. 이는 리바인딩 공격이 악용할 수 있는, 검증과 연결 사이의 틈을 막습니다.
브리지가 cURL 핀 고정 전송을 사용할 때, 검증된 주소 집합은 CURLOPT_RESOLVE로 연결에 바인딩됩니다. 따라서 커널은 연결 시점에 새로운 DNS 조회가 반환할 수 있는 주소가 아니라 검증된 주소에 연결합니다. 해당 전송에서는 리디렉션 추적이 비활성화되어 있으므로(CURLOPT_FOLLOWLOCATION 끔, CURLOPT_MAXREDIRS 0), 3xx가 요청을 검증되지 않은 호스트로 조용히 보낼 수 없습니다. 대신 응답이 정책 계층에 그대로 드러납니다.
운영상의 결과. SSRF 가드는 설계상 사설 및 예약된 주소를 거부합니다. Gotenberg가 사설 네트워크에서 실행되는 경우, 브리지가 그 사설 주소를 가리키도록 할 수 없습니다. 아래 배포 항목에서 설명하는 대로, 가드가 허용하는 주소로 노출하고 네트워크 분할 및 인증으로 해당 경로를 보호해야 합니다.
전송 보안 및 공개 키 핀 고정
섹션 제목: “전송 보안 및 공개 키 핀 고정”cURL 핀 고정 전송에서는 TLS 피어 및 호스트 검증이 항상 켜져 있습니다(CURLOPT_SSL_VERIFYPEER true, CURLOPT_SSL_VERIFYHOST 2). 표준 체인 검증에 더해 브리지는 SubjectPublicKeyInfo(SPKI) 핀 고정을 지원합니다.
각 핀은 서버의 SubjectPublicKeyInfo에 대한 SHA-256 해시이며, sha256/<base64> 형식으로 표현됩니다. 브리지는 SPKI 해시가 기본 핀과 백업 핀을 합친 집합 중 어느 하나와 일치하는 인증서를 허용합니다. 이 백업 핀 모델은 RFC 7469 §4.3을 따릅니다. 여기서는 백업 핀 — 아직 배포되지 않은 보조 키 쌍의 지문 — 을 의도치 않은 핀 검증 실패에서 복구하는 주요 방법으로 규정하며, §2.5에서는 핀으로 고정된 집합에 현재 인증서 체인에 존재하지 않는 핀을 최소 하나 포함하도록 요구합니다(RFC 7469 §4.3 및 §2.5; 이 페이지의 RAG 사이드카에 고정됨). 브리지의 코드는 최소 하나의 백업 핀 및 결합 집합 교차 의미론에 대해 RFC 7469 §2.1 및 §2.6을 선언합니다. 핀 고정은 옵트인 방식입니다. 핀이 구성되지 않으면 표준 체인 검증이 적용되고 핀 고정은 적용되지 않습니다.
파싱할 수 없는 핀은 어떤 요청보다도 먼저 구성 오류를 발생시킵니다. 실제 인증서의 SPKI가 구성된 어떤 핀과도 일치하지 않으면 전송이 요청을 실패시킵니다. 이는 설계된 동작입니다.
핀 교체 절차
섹션 제목: “핀 교체 절차”잘못된 교체는 브리지가 서비스에 접근하지 못하도록 차단할 수 있습니다. 다음 절차로 중단 없이 교체합니다.
- 서버 키를 변경하기 전에, 새 키에 대한 SPKI 핀을 생성하여 백업 핀 목록에 추가합니다. 해당 구성을 배포합니다. 이제 브리지는 현재 키와 미래 키를 모두 허용합니다.
- 서버 인증서 또는 키를 새 키로 전환합니다.
- 변환이 여전히 성공하는지 확인합니다(이제 새 키가 백업 핀과 일치합니다).
- 새 핀을 백업 목록에서 기본 목록으로 옮기고, 폐기된 키의 핀을 제거합니다. 배포합니다.
- 다음 교체를 위한 핀을 새 백업으로 생성해 준비해 둡니다. 이렇게 하면 집합이 항상 사용 가능한 예비 핀을 갖게 됩니다.
백업 목록을 기본 목록과 분리해 두면 활성 핀을 건드리지 않고 다음 핀을 준비하고 검증할 수 있습니다.
apiKey가 비어 있지 않으면 변환 요청에 Authorization: Bearer 헤더로 전송됩니다. 이 필드는 #[\SensitiveParameter]로 표시되어 있어 값이 스택 트레이스에서 가려집니다. 브리지는 시크릿을 대신 가져와 주지 않습니다. 프로세스 시작 시 시크릿 관리자에서 공급하고, 절대 커밋하지 않아야 합니다. 토큰은 요청 로그에 기록되지 않습니다 — 기록되는 디버그 항목에는 URL, 파일 이름, 형식, 콘텐츠 길이만 포함됩니다.
응답 신뢰 경계
섹션 제목: “응답 신뢰 경계”응답은 상태가 200이고, Content-Type에 application/pdf가 포함되며, 본문이 %PDF 서명으로 시작할 때에만 허용됩니다. 바이트 서명 검사가 필요한 이유는 선언된 콘텐츠 유형만으로는 바이트가 무엇인지 확정할 수 없기 때문입니다 — 이는 WHATWG MIME Sniffing 표준이 MIME 유형 스니핑 알고리즘에서 공식화한 논리와 같습니다. 이 알고리즘은 제공된 유형이 아니라 선행 바이트 패턴 일치를 기준으로 계산된 유형을 도출합니다. OWASP 파일 업로드 지침은 이에 상응하는 애플리케이션 원칙, 즉 파일 유형을 검증하고 선언된 Content-Type 헤더는 위조될 수 있으므로 신뢰하지 말 것을 명시합니다(WHATWG MIME Sniffing §6.2.3; OWASP Cheat Sheet Series, 파일 업로드 검증; 둘 다 이 페이지의 RAG 사이드카에 고정됨). 브리지는 인바운드 측에 같은 방어적 검사를 적용합니다. 불일치는 형식이 지정된 예외를 발생시키며, 해당 바이트는 결과로 반환되지 않습니다.
이 경계 때문에 여기서 PSR-18 계약도 중요합니다. PSR-18 클라이언트는 요청을 전송할 수 없거나 응답을 PSR-7 객체로 파싱할 수 없을 때에만 예외를 던집니다 — 오류 상태 코드에서는 예외를 던지지 않습니다. 올바른 형식의 4xx/5xx 응답은 정상적으로 호출자에게 반환됩니다(PSR-18, “Exceptions”; 이 페이지의 RAG 사이드카에 고정됨). 따라서 브리지는 반환된 응답이 성공 응답이라고 가정하지 않고 상태, 유형, 서명을 직접 검사합니다. content-type 제약 위반에 대한 HTTP 의미론은 서버가 지원되지 않는 형식의 콘텐츠를 거부하는 415(Unsupported Media Type) 거부이며, 이는 인바운드 검사가 본뜬 모델입니다(RFC 9110 §15.5.16; 이 페이지의 RAG 사이드카에 고정됨).
리소스 제한
섹션 제목: “리소스 제한”프로세스 내에서 유일한 리소스 한계는 maxFileSize(기본값 52,428,800 바이트 = 50 MiB)이며, 요청 전에 적용되므로 크기가 초과된 입력은 결코 서비스에 도달하지 않습니다. 브리지에는 내장된 동시성 제한, 속도 제한, 출력 크기 상한이 없습니다. 이는 배포 환경과 호출자의 책임입니다(/integrations/gotenberg/production-usage/ 참조). maxFileSize를 실제 문서 처리에 필요한 최소값으로 설정해야 합니다 — 더 엄격한 상한은 더 저렴한 서비스 거부 방어책입니다.
Gotenberg 서비스의 배포 및 보안
섹션 제목: “Gotenberg 서비스의 배포 및 보안”브리지는 자신이 호출하는 서비스만큼만 안전합니다. 서비스 운영은 여러분의 책임이며, 아래의 책무가 위의 제어를 완성합니다.
- Gotenberg 앞단에서 TLS를 종료해야 합니다. Gotenberg의 컨테이너는 기본적으로 일반 HTTP로 통신합니다. 브리지는 HTTPS를 요구하므로, Gotenberg를 TLS를 종료하는 리버스 프록시, 인그레스, 또는 서비스 메시 뒤에 두고 브리지가 HTTPS 엔드포인트를 가리키도록 해야 합니다. 핀 고정을 활성화하는 경우 프록시의 SPKI를 핀 고정해야 합니다.
- Gotenberg를 공개적으로 노출하지 않아야 합니다. 이 서비스는 LibreOffice 및 Chromium 계열 엔진으로 문서 변환을 수행하며, 인터넷에 직접 노출할 서비스가 아닙니다. 네트워크 정책이나 방화벽으로 인그레스를 이를 호출하는 애플리케이션 호스트로 제한해야 합니다.
- 경로에 인증을 요구해야 합니다. 브리지는 구성된 경우 베어러 토큰을 전송합니다. 인증되지 않은 요청이 변환 엔진에 도달하지 못하도록 프록시에서 이를(또는 상호 TLS를) 강제해야 합니다.
- 특정 서비스 버전을 고정해야 합니다. 브리지는 정확히 두 개의 서비스 경로 —
/forms/libreoffice/convert및/health— 를 가정합니다. Gotenberg 이미지를 특정 패치 태그로 고정하고, 배포하는 버전에 대해 그 두 경로를 검증하며, 업그레이드할 때마다 다시 검증해야 합니다. - 변환 용량을 신중하게 산정해야 합니다. 각 변환은 요청이 지속되는 동안 워커를 점유합니다. 최대 동시 변환 수에 맞춰 Gotenberg 배포 규모를 산정하고, 이에 맞게 호출자 측에서 진행 중인 변환 수를 제한해야 합니다. 용량은 이 패키지의 속성이 아니라 여러분의 배포 환경 속성입니다.
- 변환 입력은 신뢰할 수 없는 것으로 취급해야 합니다. 변환에 전달되는 문서는 복잡한 엔진에 의해 처리됩니다.
maxFileSize를 제한하고, Gotenberg 배포를 격리하며(자체 네트워크 세그먼트, 최소한의 이그레스, 내부 서비스 접근 불가), 엔진을 패치된 상태로 유지해야 합니다.
이 패키지가 주장하지 않는 것
섹션 제목: “이 패키지가 주장하지 않는 것”- 이는 “기본적으로 안전”하지 않습니다. 제어는 실제로 존재하지만 올바른 배포와 구성에 의존합니다.
- 이는 변환을 “변조 불가능”하게 만들거나 출력을 “인증된” 것으로 만들지 않습니다. 이는 전송과 응답 형식을 검증할 뿐, 문서 내용을 입증하지는 않습니다.
- 이는 서명, 타임스탬프, 또는 장기 검증을 제공하지 않습니다. 이들은 후처리 영역입니다. Pro 에디션의 PAdES 지원은 B-B 베이스라인뿐이며 B-T, B-LT, 또는 B-LTA를 포함하지 않습니다. 이 브리지의 어떤 것도 타임스탬프 또는 LTV 기능을 암시하지 않습니다.
- 이는 “모든 Office 파일”을 지원하지 않습니다. 이는 열거된 여섯 가지 형식을 지원하며, 그 외의 모든 것은 어떤 요청보다도 먼저 거부합니다.
함께 보기
섹션 제목: “함께 보기”- /integrations/gotenberg/configuration/ — 전송 선택 규칙과 전체 핀 모델을 다룹니다.
- /integrations/gotenberg/production-usage/ — 재시도, 타임아웃, 동시성, 관측성을 다룹니다.
- /integrations/gotenberg/troubleshooting/ — 모든 보안 예외와 그 트리거를 다룹니다.
- /integrations/gotenberg/overview/ — 변환 흐름과 의존성 모델을 다룹니다.