1 Commits

Author SHA1 Message Date
65d5dfdbd6 fix(security): exempt health endpoint from HTTPS enforcement
Docker health checks and load balancers call /health directly without
going through the reverse proxy, so they need HTTP access. This fix
exempts /health and /metrics endpoints from HTTPS enforcement in
production mode.

Fixes the issue where Docker health checks were being redirected to
HTTPS and failing because there's no TLS on localhost.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 11:45:06 -07:00
2 changed files with 74 additions and 0 deletions

View File

@@ -12,6 +12,11 @@ from gondulf.config import Config
logger = logging.getLogger("gondulf.middleware.https_enforcement")
# Internal endpoints exempt from HTTPS enforcement
# These are called by Docker health checks, load balancers, and monitoring systems
# that connect directly to the container without going through the reverse proxy.
HTTPS_EXEMPT_PATHS = {"/health", "/metrics"}
def is_https_request(request: Request) -> bool:
"""
@@ -93,6 +98,12 @@ class HTTPSEnforcementMiddleware(BaseHTTPMiddleware):
# Continue processing
return await call_next(request)
# Exempt internal endpoints from HTTPS enforcement
# These are used by Docker health checks, load balancers, etc.
# that connect directly without going through the reverse proxy.
if request.url.path in HTTPS_EXEMPT_PATHS:
return await call_next(request)
# Production mode: Enforce HTTPS
if not is_https_request(request):
logger.warning(

View File

@@ -67,3 +67,66 @@ class TestHTTPSEnforcement:
response = client.get("/")
# TestClient doesn't enforce HTTPS, but middleware should allow it
assert response.status_code == 200
def test_health_endpoint_exempt_from_https_in_production(
self, client, monkeypatch
):
"""Test /health endpoint is accessible via HTTP in production mode.
Docker health checks and load balancers call the health endpoint directly
without going through the reverse proxy, so it must work over HTTP.
The key assertion is that we don't get a 301 redirect to HTTPS.
"""
from gondulf.config import Config
monkeypatch.setattr(Config, "DEBUG", False)
monkeypatch.setattr(Config, "TRUST_PROXY", False)
# HTTP request to /health should NOT redirect to HTTPS
response = client.get(
"http://localhost:8000/health", follow_redirects=False
)
# Should NOT be 301 redirect - actual status depends on DB state (200/503)
assert response.status_code != 301
# Verify it reached the health endpoint (not redirected)
assert response.status_code in (200, 503)
def test_health_endpoint_head_request_in_production(self, client, monkeypatch):
"""Test HEAD request to /health is not redirected in production.
Docker health checks may use HEAD requests. The key is that the
middleware doesn't redirect to HTTPS - the actual endpoint behavior
(405 Method Not Allowed) is separate from HTTPS enforcement.
"""
from gondulf.config import Config
monkeypatch.setattr(Config, "DEBUG", False)
monkeypatch.setattr(Config, "TRUST_PROXY", False)
# HEAD request to /health should NOT redirect to HTTPS
response = client.head(
"http://localhost:8000/health", follow_redirects=False
)
# Should NOT be 301 redirect
assert response.status_code != 301
def test_metrics_endpoint_exempt_from_https_in_production(
self, client, monkeypatch
):
"""Test /metrics endpoint is accessible via HTTP in production mode.
Monitoring systems may call metrics directly without HTTPS.
"""
from gondulf.config import Config
monkeypatch.setattr(Config, "DEBUG", False)
monkeypatch.setattr(Config, "TRUST_PROXY", False)
# HTTP request to /metrics should not be redirected
# (endpoint may not exist yet, but should not redirect to HTTPS)
response = client.get(
"http://localhost:8000/metrics", follow_redirects=False
)
# Should return 404 (not found) not 301 (redirect to HTTPS)
assert response.status_code != 301