From 045f73c9985a69975e05a092eddf8a9c7e13f5a6 Mon Sep 17 00:00:00 2001 From: Username Date: Wed, 24 Dec 2025 16:41:31 +0100 Subject: [PATCH] feat: integrate unused observability features - Add request duration metrics via before/after request hooks - Add PKI audit logging: CERT_ISSUED, CERT_REVOKED, AUTH_FAILURE - Wire up observe_request_duration() from metrics.py - Log certificate operations (registration, CA gen, issue, revoke) - Log auth failures for revoked/expired certificates --- app/__init__.py | 29 +++++++++++++++++++++++++ app/api/routes.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index 0fc5eb9..e460067 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,6 +3,7 @@ import logging import os import sys +import time import uuid from flask import Flask, Response, g, request @@ -92,6 +93,30 @@ def setup_request_id(app: Flask) -> None: return response +def setup_request_metrics(app: Flask) -> None: + """Record request duration metrics for Prometheus.""" + from app.metrics import observe_request_duration + + @app.before_request + def record_request_start() -> None: + """Record request start time for duration metrics.""" + g.request_start_time = time.time() + + @app.after_request + def record_request_duration(response: Response) -> Response: + """Record request duration to Prometheus histogram.""" + start_time = getattr(g, "request_start_time", None) + if start_time is not None: + duration = time.time() - start_time + observe_request_duration( + method=request.method, + endpoint=request.path, + status=response.status_code, + duration=duration, + ) + return response + + def setup_error_handlers(app: Flask) -> None: """Register global error handlers with JSON responses.""" import json @@ -226,6 +251,10 @@ def create_app(config_name: str | None = None) -> Flask: # Setup metrics (skip in testing) setup_metrics(app) + # Setup request duration metrics (skip in testing) + if not app.config.get("TESTING"): + setup_request_metrics(app) + # Initialize database from app import database diff --git a/app/api/routes.py b/app/api/routes.py index 5b95326..09907b6 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -17,6 +17,7 @@ from flask import Response, current_app, g, request from flask.views import MethodView from app.api import bp +from app.audit import AuditEvent, AuditOutcome, log_event from app.config import VERSION from app.database import check_content_hash, get_db, hash_password, verify_password from app.metrics import ( @@ -445,6 +446,13 @@ def get_client_id() -> str | None: current_app.logger.warning( "Elevated auth rejected (revoked/expired): %s", fingerprint[:12] + "..." ) + log_event( + AuditEvent.AUTH_FAILURE, + AuditOutcome.BLOCKED, + client_id=fingerprint, + client_ip=get_client_ip(), + details={"reason": "revoked_or_expired"}, + ) return None return fingerprint @@ -1205,6 +1213,17 @@ class RegisterView(MethodView): cert_info["fingerprint_sha1"][:12], request.remote_addr, ) + log_event( + AuditEvent.CERT_ISSUED, + AuditOutcome.SUCCESS, + client_id=cert_info["fingerprint_sha1"], + client_ip=request.remote_addr, + details={ + "type": "registration", + "common_name": common_name, + "expires_at": cert_info["expires_at"], + }, + ) # Return PKCS#12 as binary download response = Response(p12_data, mimetype="application/x-pkcs12") @@ -1745,6 +1764,18 @@ class PKICAGenerateView(MethodView): current_app.logger.info( "CA generated: cn=%s fingerprint=%s", common_name, ca_info["fingerprint_sha1"][:12] ) + log_event( + AuditEvent.CERT_ISSUED, + AuditOutcome.SUCCESS, + client_id=owner, + client_ip=request.remote_addr, + details={ + "type": "ca", + "fingerprint": ca_info["fingerprint_sha1"][:16], + "common_name": common_name, + "expires_at": ca_info["expires_at"], + }, + ) return json_response( { @@ -1829,6 +1860,19 @@ class PKIIssueView(MethodView): cert_info["fingerprint_sha1"][:12], issued_to or "anonymous", ) + log_event( + AuditEvent.CERT_ISSUED, + AuditOutcome.SUCCESS, + client_id=cert_info["fingerprint_sha1"], + client_ip=request.remote_addr, + details={ + "type": "client", + "serial": cert_info["serial"][:16], + "common_name": common_name, + "issued_by": issued_to, + "expires_at": cert_info["expires_at"], + }, + ) # Return certificate bundle return json_response( @@ -1936,6 +1980,16 @@ class PKIRevokeView(MethodView): return error_response("Revocation failed", 500) current_app.logger.info("Certificate revoked: serial=%s by=%s", serial[:8], client_id[:12]) + log_event( + AuditEvent.CERT_REVOKED, + AuditOutcome.SUCCESS, + client_id=client_id, + client_ip=get_client_ip(), + details={ + "serial": serial[:16], + "fingerprint": row["fingerprint_sha1"][:16], + }, + ) return json_response({"message": "Certificate revoked", "serial": serial})