feat: integrate unused observability features
All checks were successful
CI / Lint & Format (push) Successful in 18s
CI / Security Scan (push) Successful in 23s
CI / Memory Leak Check (push) Successful in 22s
CI / Tests (push) Successful in 1m16s

- 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
This commit is contained in:
Username
2025-12-24 16:41:31 +01:00
parent fef5eac1b5
commit 045f73c998
2 changed files with 83 additions and 0 deletions

View File

@@ -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

View File

@@ -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})