Enforce deployment security boundaries

This commit is contained in:
2026-07-21 12:10:05 +02:00
parent 825791e9b0
commit 230ecf42b0
10 changed files with 629 additions and 28 deletions

View File

@@ -4,8 +4,9 @@ import shutil
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from fastapi import APIRouter
from fastapi import APIRouter, Request
from fastapi.testclient import TestClient
from govoplan_core.audit.logging import audit_event, audit_operation_context
@@ -142,6 +143,75 @@ class CoreEventTests(unittest.TestCase):
cors_origins=("*",),
)
def test_app_sets_safe_default_browser_headers_and_https_hsts(self) -> None:
with patch.dict(
"os.environ",
{"APP_ENV": "test", "GOVOPLAN_HTTP_HSTS_SECONDS": "86400"},
):
app = create_govoplan_app(
title="security header test",
version="test",
registry=PlatformRegistry(),
)
with TestClient(app, base_url="https://govoplan.example.test") as client:
response = client.get("/health")
self.assertEqual("nosniff", response.headers["X-Content-Type-Options"])
self.assertEqual("DENY", response.headers["X-Frame-Options"])
self.assertEqual("strict-origin-when-cross-origin", response.headers["Referrer-Policy"])
self.assertIn("frame-ancestors 'none'", response.headers["Content-Security-Policy"])
self.assertEqual("max-age=86400", response.headers["Strict-Transport-Security"])
def test_production_app_startup_rejects_an_unvalidated_environment(self) -> None:
with patch.dict("os.environ", {"APP_ENV": "production"}, clear=True), self.assertRaisesRegex(
RuntimeError,
"GovOPlaN configuration validation: FAILED",
):
create_govoplan_app(
title="unsafe production test",
version="test",
registry=PlatformRegistry(),
)
def test_app_rejects_request_bodies_above_deployment_limit(self) -> None:
router = APIRouter()
@router.post("/body")
async def body(request: Request):
return {"size": len(await request.body())}
with patch.dict("os.environ", {"GOVOPLAN_HTTP_MAX_REQUEST_BODY_BYTES": "10"}):
app = create_govoplan_app(
title="request body limit test",
version="test",
registry=PlatformRegistry(),
api_router=router,
)
with TestClient(app) as client:
response = client.post("/body", content=b"12345678901")
streamed = client.post("/body", content=(chunk for chunk in (b"123456", b"78901")))
self.assertEqual(413, response.status_code, response.text)
self.assertIn("10 bytes", response.json()["detail"])
self.assertEqual(413, streamed.status_code, streamed.text)
def test_app_enforces_configured_trusted_hosts(self) -> None:
with patch.dict("os.environ", {"GOVOPLAN_TRUSTED_HOSTS": "govoplan.example.test,*.internal.test"}):
app = create_govoplan_app(
title="trusted host test",
version="test",
registry=PlatformRegistry(),
)
with TestClient(app, base_url="https://govoplan.example.test") as client:
allowed = client.get("/health")
rejected = client.get("https://attacker.example.test/health")
self.assertEqual(200, allowed.status_code, allowed.text)
self.assertEqual(400, rejected.status_code, rejected.text)
def test_audit_event_persists_trace_details_from_event_context(self) -> None:
root = Path(tempfile.mkdtemp(prefix="govoplan-audit-trace-"))
try: