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>
133 lines
4.9 KiB
Python
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
|
|
|