Files
Gondulf/tests/integration/test_https_enforcement.py
Phil Skentelbery 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

133 lines
4.9 KiB
Python

"""Integration tests for HTTPS enforcement middleware."""
import tempfile
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
@pytest.fixture
def test_app(monkeypatch):
"""Create test FastAPI app with test configuration."""
# Set up test environment
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test.db"
# Set required environment variables
monkeypatch.setenv("GONDULF_SECRET_KEY", "a" * 32)
monkeypatch.setenv("GONDULF_BASE_URL", "https://auth.example.com")
monkeypatch.setenv("GONDULF_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("GONDULF_DEBUG", "true")
# Import app AFTER setting env vars
from gondulf.main import app
yield app
@pytest.fixture
def client(test_app):
"""FastAPI test client."""
return TestClient(test_app)
class TestHTTPSEnforcement:
"""Test HTTPS enforcement middleware."""
def test_https_allowed_in_production(self, client, monkeypatch):
"""Test HTTPS requests are allowed in production mode."""
# Simulate production mode
from gondulf.config import Config
monkeypatch.setattr(Config, "DEBUG", False)
# HTTPS request should succeed
# Note: TestClient uses http by default, so this test is illustrative
# In real production, requests come from a reverse proxy (nginx) with HTTPS
# Use root endpoint instead of health as it doesn't require database
response = client.get("/")
assert response.status_code == 200
def test_http_localhost_allowed_in_debug(self, client, monkeypatch):
"""Test HTTP to localhost is allowed in debug mode."""
from gondulf.config import Config
monkeypatch.setattr(Config, "DEBUG", True)
# HTTP to localhost should succeed in debug mode
# Use root endpoint instead of health as it doesn't require database
response = client.get("http://localhost:8000/")
assert response.status_code == 200
def test_https_always_allowed(self, client):
"""Test HTTPS requests are always allowed regardless of mode."""
# HTTPS should work in both debug and production
# Use root endpoint instead of health as it doesn't require database
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