From cd02fabb2ea150ccbfd42e88b8efd824b3dd318d Mon Sep 17 00:00:00 2001 From: Vladimir Bryzgalov Date: Sun, 16 Aug 2026 19:00:49 +0500 Subject: [PATCH] =?UTF-8?q?Caddyfile=20=D0=B1=D0=B5=D0=B7=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D1=83=D0=BF=D1=80=D0=B5=D0=B6=D0=B4=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=BF=D1=80=D0=B8=20=D0=B7=D0=B0=D0=BF=D1=83?= =?UTF-8?q?=D1=81=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caddy ругался на два места. header_up X-Forwarded-For был лишним: Caddy сам игнорирует этот заголовок из входящего запроса и подставляет реальный адрес, защита от подделки обеспечивается его поведением по умолчанию. Пустая строка перед глобальным блоком делала файл неформатированным по мнению caddy fmt. Проверено caddy fmt и caddy validate на сгенерированном файле. Co-Authored-By: Claude Opus 5 (1M context) --- app/launcher.py | 13 +++++++------ tests/test_launcher.py | 22 ++++++++++++++++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/app/launcher.py b/app/launcher.py index d8991a7..e3e09f6 100644 --- a/app/launcher.py +++ b/app/launcher.py @@ -22,9 +22,10 @@ CADDY_WAIT_SEC = 3 def render_caddyfile(domains: str, port: int, max_upload_mb: int) -> str: """Собирает конфигурацию Caddy из настроек сервиса.""" listed = ", ".join(d.strip() for d in domains.split(",") if d.strip()) + # Пустой строки перед блоком быть не должно: caddy fmt считает это + # неформатированным файлом и ругается при каждом запуске. return f"""{MARK} # Домены и предел размера берутся из config.toml, менять нужно там. - {{ \t# Caddy очень подробно рассказывает про сертификаты. В консоли из-за этого \t# не видно сообщений сервиса, поэтому его вывод уходит в файл. @@ -39,11 +40,11 @@ def render_caddyfile(domains: str, port: int, max_upload_mb: int) -> str: }} {listed} {{ -\treverse_proxy 127.0.0.1:{port} {{ -\t\t# Реальный адрес клиента - иначе список разрешённых адресов -\t\t# увидит вместо него localhost и пустит кого угодно. -\t\theader_up X-Forwarded-For {{remote_host}} -\t}} +\t# X-Forwarded-For здесь намеренно не задаётся: Caddy сам игнорирует этот +\t# заголовок из входящего запроса и подставляет реальный адрес клиента. +\t# Не добавляйте сюда trusted_proxies - тогда Caddy начнёт верить заголовку +\t# от клиента, и любой сможет назваться разрешённым адресом. +\treverse_proxy 127.0.0.1:{port} \trequest_body {{ \t\tmax_size {max_upload_mb}MB diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 90c38c2..396cd5c 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -16,10 +16,19 @@ class TestRender: assert "reverse_proxy 127.0.0.1:9000" in text assert "max_size 250MB" in text - def test_real_client_address_is_forwarded(self): - """Без этого заголовка список разрешённых адресов увидит localhost.""" - text = render_caddyfile("a.example.com", 8756, 500) - assert "header_up X-Forwarded-For {remote_host}" in text + def test_xff_is_left_to_caddy_default(self): + """Caddy сам игнорирует X-Forwarded-For от клиента и ставит реальный адрес. + + Свой header_up был лишним - Caddy предупреждал об этом при каждом + запуске. Важнее обратное: trusted_proxies появляться не должен, иначе + Caddy начнёт верить заголовку клиента и адрес станет подделываемым. + """ + # Комментарии не в счёт: в них эти слова упомянуты как предостережение. + directives = "\n".join( + line for line in render_caddyfile("a.example.com", 8756, 500).splitlines() + if not line.strip().startswith("#")) + assert "header_up" not in directives + assert "trusted_proxies" not in directives def test_caddy_log_goes_to_file(self): """Иначе Caddy забивает консоль сообщениями про сертификаты.""" @@ -29,6 +38,11 @@ class TestRender: def test_generated_file_is_marked(self): assert render_caddyfile("a.example.com", 8756, 500).startswith(MARK) + def test_no_blank_line_before_global_block(self): + """caddy fmt считает такой файл неформатированным и ругается при запуске.""" + lines = render_caddyfile("a.example.com", 8756, 500).splitlines() + assert lines[lines.index("{") - 1].strip() != "" + class TestEnsure: def test_creates_file_when_missing(self, tmp_path):