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
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 logging
import os import os
import sys import sys
import time
import uuid import uuid
from flask import Flask, Response, g, request from flask import Flask, Response, g, request
@@ -92,6 +93,30 @@ def setup_request_id(app: Flask) -> None:
return response 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: def setup_error_handlers(app: Flask) -> None:
"""Register global error handlers with JSON responses.""" """Register global error handlers with JSON responses."""
import json import json
@@ -226,6 +251,10 @@ def create_app(config_name: str | None = None) -> Flask:
# Setup metrics (skip in testing) # Setup metrics (skip in testing)
setup_metrics(app) setup_metrics(app)
# Setup request duration metrics (skip in testing)
if not app.config.get("TESTING"):
setup_request_metrics(app)
# Initialize database # Initialize database
from app import 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 flask.views import MethodView
from app.api import bp from app.api import bp
from app.audit import AuditEvent, AuditOutcome, log_event
from app.config import VERSION from app.config import VERSION
from app.database import check_content_hash, get_db, hash_password, verify_password from app.database import check_content_hash, get_db, hash_password, verify_password
from app.metrics import ( from app.metrics import (
@@ -445,6 +446,13 @@ def get_client_id() -> str | None:
current_app.logger.warning( current_app.logger.warning(
"Elevated auth rejected (revoked/expired): %s", fingerprint[:12] + "..." "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 None
return fingerprint return fingerprint
@@ -1205,6 +1213,17 @@ class RegisterView(MethodView):
cert_info["fingerprint_sha1"][:12], cert_info["fingerprint_sha1"][:12],
request.remote_addr, 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 # Return PKCS#12 as binary download
response = Response(p12_data, mimetype="application/x-pkcs12") response = Response(p12_data, mimetype="application/x-pkcs12")
@@ -1745,6 +1764,18 @@ class PKICAGenerateView(MethodView):
current_app.logger.info( current_app.logger.info(
"CA generated: cn=%s fingerprint=%s", common_name, ca_info["fingerprint_sha1"][:12] "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( return json_response(
{ {
@@ -1829,6 +1860,19 @@ class PKIIssueView(MethodView):
cert_info["fingerprint_sha1"][:12], cert_info["fingerprint_sha1"][:12],
issued_to or "anonymous", 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 certificate bundle
return json_response( return json_response(
@@ -1936,6 +1980,16 @@ class PKIRevokeView(MethodView):
return error_response("Revocation failed", 500) return error_response("Revocation failed", 500)
current_app.logger.info("Certificate revoked: serial=%s by=%s", serial[:8], client_id[:12]) 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}) return json_response({"message": "Certificate revoked", "serial": serial})