Files
DECNET/tests/services/test_caddyfile_render.py
anti 0653e500b5 feat(services): HTTP/2 + HTTP/3 support via Caddy reverse-proxy
Swap Werkzeug for Caddy as the protocol layer for http and https decoy
services. Flask keeps owning app logic (fake_app, custom_body, headers,
syslog) on 127.0.0.1:8080; Caddy terminates h1/h2/h2c/h3 on the wire
with real-world TLS/QUIC fingerprints.

- Add `multi_enum` FieldType to ServiceConfigField + _coerce
- Add `http_versions` field to HTTPService (h1/h2c) and HTTPSService
  (h1/h2/h3); selecting h3 emits UDP/443 port mapping in compose
- Rewrite both Dockerfiles with multi-stage Caddy binary copy +
  setcap for port binding as the logrelay user
- Entrypoints parse HTTP_VERSIONS JSON, render a Caddyfile, start
  Flask in background, wait for it, then exec Caddy
- https/server.py drops direct TLS handling; Caddy owns the cert
- Add ProxyFix to both server.py so Flask sees real attacker IPs
- Frontend: multi_enum checkbox-group renderer in ServiceConfigFields;
  FormValue union extended to string[]; compactPayload skips []
- Fix stale test_smtp_relay_schema_matches_smtp: relay schema is a
  superset of smtp, not equal; update assertions accordingly
2026-05-10 00:04:37 -04:00

76 lines
2.2 KiB
Python

"""Caddyfile protocol token generation for http and https entrypoints."""
import json
def _http_protocols(versions_json: str | None) -> str:
"""Mirrors the Python inline logic in templates/http/entrypoint.sh."""
versions = json.loads(versions_json or '["http/1.1"]')
tokens = []
if "http/1.1" in versions:
tokens.append("h1")
if "http/2" in versions:
tokens.append("h2c")
return " ".join(tokens) if tokens else "h1"
def _https_protocols(versions_json: str | None) -> str:
"""Mirrors the Python inline logic in templates/https/entrypoint.sh."""
versions = json.loads(versions_json or '["http/1.1"]')
tokens = []
if "http/1.1" in versions:
tokens.append("h1")
if "http/2" in versions:
tokens.append("h2")
if "http/3" in versions:
tokens.append("h3")
return " ".join(tokens) if tokens else "h1"
# ---------------------------------------------------------------------------
# HTTP (cleartext) protocol token tests
# ---------------------------------------------------------------------------
def test_http_h1_only():
assert _http_protocols('["http/1.1"]') == "h1"
def test_http_h1_and_h2c():
assert _http_protocols('["http/1.1", "http/2"]') == "h1 h2c"
def test_http_h2c_only():
assert _http_protocols('["http/2"]') == "h2c"
def test_http_default_fallback():
assert _http_protocols(None) == "h1"
def test_http_empty_versions_fallback():
# Should not happen (coercion rejects empty list) but guard the fallback.
assert _http_protocols("[]") == "h1"
# ---------------------------------------------------------------------------
# HTTPS (TLS) protocol token tests
# ---------------------------------------------------------------------------
def test_https_h1_only():
assert _https_protocols('["http/1.1"]') == "h1"
def test_https_h1_and_h2():
assert _https_protocols('["http/1.1", "http/2"]') == "h1 h2"
def test_https_all_three():
assert _https_protocols('["http/1.1", "http/2", "http/3"]') == "h1 h2 h3"
def test_https_h3_only():
assert _https_protocols('["http/3"]') == "h3"
def test_https_default_fallback():
assert _https_protocols(None) == "h1"