forked from claw/flaskpaste
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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user