Ephemeral keys for xterm.js. Initial rework of audit logging. All endpoints now return a 401 regardless of presence if not logged in.
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import time
|
||||
|
||||
from django.utils import timezone
|
||||
from django.utils.text import slugify
|
||||
|
||||
from .matching import find_matching_event_type
|
||||
from .models import AuditEventType, AuditLog
|
||||
from .utils import get_client_ip, get_request_id
|
||||
|
||||
_EVENT_CACHE: dict[str, AuditEventType] = {}
|
||||
_SKIP_PREFIXES = ("/api/v1/audit", "/api/v1/user")
|
||||
_SKIP_SUFFIXES = ("/health", "/health/")
|
||||
|
||||
@@ -18,6 +16,8 @@ def _is_api_request(path: str) -> bool:
|
||||
|
||||
|
||||
def _should_log_request(path: str) -> bool:
|
||||
# Only audit API traffic and skip endpoints that would recursively
|
||||
# generate noisy audit events (audit endpoints, health checks, etc.).
|
||||
if not _is_api_request(path):
|
||||
return False
|
||||
if path in _SKIP_PREFIXES:
|
||||
@@ -37,46 +37,12 @@ def _resolve_route(request, fallback: str) -> str:
|
||||
return fallback
|
||||
|
||||
|
||||
def _event_key_for(method: str, route: str) -> str:
|
||||
base = f"api_{method.lower()}_{route}"
|
||||
slug = slugify(base)
|
||||
if not slug:
|
||||
return "api_request"
|
||||
if len(slug) <= 64:
|
||||
return slug
|
||||
digest = hashlib.sha1(slug.encode("utf-8")).hexdigest()[:8]
|
||||
prefix_len = 64 - len(digest) - 1
|
||||
return f"{slug[:prefix_len]}-{digest}"
|
||||
|
||||
|
||||
def _event_title_for(method: str, route: str) -> str:
|
||||
title = f"API {method.upper()} {route}"
|
||||
if len(title) <= 128:
|
||||
return title
|
||||
return f"{title[:125]}..."
|
||||
|
||||
|
||||
def _get_endpoint_event(method: str, route: str) -> AuditEventType:
|
||||
key = _event_key_for(method, route)
|
||||
cached = _EVENT_CACHE.get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
event, _ = AuditEventType.objects.get_or_create(
|
||||
key=key,
|
||||
defaults={
|
||||
"title": _event_title_for(method, route),
|
||||
"default_severity": AuditEventType.Severity.INFO,
|
||||
},
|
||||
)
|
||||
_EVENT_CACHE[key] = event
|
||||
return event
|
||||
|
||||
|
||||
class ApiAuditLogMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
# Fast-exit for non-audited paths before taking timing measurements.
|
||||
path = request.path_info or request.path
|
||||
if not _should_log_request(path):
|
||||
return self.get_response(request)
|
||||
@@ -96,8 +62,21 @@ class ApiAuditLogMiddleware:
|
||||
def _write_log(self, request, path: str, status_code: int, duration_ms: int, error: str | None = None) -> None:
|
||||
try:
|
||||
route = _resolve_route(request, path)
|
||||
client_ip = get_client_ip(request)
|
||||
# Audit events are explicit: if no configured event type matches,
|
||||
# we do not create either an event type or a log entry.
|
||||
event_type = find_matching_event_type(
|
||||
kind=AuditEventType.Kind.API,
|
||||
method=request.method,
|
||||
route=route,
|
||||
path=path,
|
||||
ip=client_ip,
|
||||
)
|
||||
if event_type is None:
|
||||
return
|
||||
user = getattr(request, "user", None)
|
||||
actor = user if getattr(user, "is_authenticated", False) else None
|
||||
# Store normalized request context for filtering and forensics.
|
||||
metadata = {
|
||||
"method": request.method,
|
||||
"path": path,
|
||||
@@ -111,11 +90,11 @@ class ApiAuditLogMiddleware:
|
||||
AuditLog.objects.create(
|
||||
created_at=timezone.now(),
|
||||
actor=actor,
|
||||
event_type=_get_endpoint_event(request.method, route),
|
||||
event_type=event_type,
|
||||
message=f"API request {request.method} {route} -> {status_code}",
|
||||
severity=AuditEventType.Severity.INFO,
|
||||
severity=event_type.default_severity,
|
||||
source=AuditLog.Source.API,
|
||||
ip_address=get_client_ip(request),
|
||||
ip_address=client_ip,
|
||||
user_agent=request.META.get("HTTP_USER_AGENT", ""),
|
||||
request_id=get_request_id(request),
|
||||
metadata=metadata,
|
||||
|
||||
Reference in New Issue
Block a user