security: implement HTTP access log API key redaction

- Add AccessLogFormatter class with regex patterns for API key detection
- Create setup_access_logging() function to configure uvicorn access logs
- Support redaction for Google/Gemini (AIza*) and OpenAI (sk-*) API keys
- Configure main.py to use custom access logging with redaction
- Prevent API key exposure in HTTP access logs like "POST /verify-key/AIza..."

Now access logs show: "POST /verify-key/AIzaxx...xyzxyz" instead of full keys
This commit is contained in:
Shuai Lin
2025-07-21 10:09:29 +08:00
parent 6abda7d902
commit f3d9cb2b85
2 changed files with 94 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
import logging
import platform
import sys
import re
from typing import Dict, Optional
# ANSI转义序列颜色代码
@@ -12,6 +13,17 @@ COLORS = {
"CRITICAL": "\033[1;31m", # 红色加粗
}
def _redact_key_for_logging(key: str) -> str:
"""
Redacts API key for secure logging by showing only first and last 6 characters.
(Internal function to avoid circular imports)
"""
if not key or len(key) <= 12:
return "***"
return f"{key[:6]}...{key[-6:]}"
# Windows系统启用ANSI支持
if platform.system() == "Windows":
import ctypes
@@ -35,6 +47,44 @@ class ColoredFormatter(logging.Formatter):
return super().format(record)
class AccessLogFormatter(logging.Formatter):
"""
Custom access log formatter that redacts API keys in URLs
"""
# API key patterns to match in URLs
API_KEY_PATTERNS = [
r'AIza[0-9A-Za-z_-]{35}', # Google API keys (like Gemini)
r'sk-[0-9A-Za-z]{48}', # OpenAI API keys
r'sk-[0-9A-Za-z_-]{20,}', # General sk- prefixed keys
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Compile regex patterns for better performance
self.compiled_patterns = [re.compile(pattern) for pattern in self.API_KEY_PATTERNS]
def format(self, record):
# Format the record normally first
formatted_msg = super().format(record)
# Redact API keys in the formatted message
return self._redact_api_keys_in_message(formatted_msg)
def _redact_api_keys_in_message(self, message: str) -> str:
"""
Replace API keys in log message with redacted versions
"""
for pattern in self.compiled_patterns:
def replace_key(match):
key = match.group(0)
return _redact_key_for_logging(key)
message = pattern.sub(replace_key, message)
return message
# 日志格式 - 使用 fileloc 并设置固定宽度 (例如 30)
FORMATTER = ColoredFormatter(
"%(asctime)s | %(levelname)-17s | %(fileloc)-30s | %(message)s"
@@ -235,3 +285,43 @@ def get_files_logger():
def get_vertex_express_logger():
return Logger.setup_logger("vertex_express")
def setup_access_logging():
"""
Configure uvicorn access logging with API key redaction
This function sets up a custom access log formatter that automatically
redacts API keys in HTTP access logs. It works by:
1. Intercepting uvicorn's access log messages
2. Using regex patterns to find API keys in URLs
3. Replacing them with redacted versions (first6...last6)
Supported API key formats:
- Google/Gemini API keys: AIza[35 chars]
- OpenAI API keys: sk-[48 chars]
- General sk- prefixed keys: sk-[20+ chars]
Usage:
- Automatically called in main.py when running with uvicorn
- For production deployment with gunicorn, ensure this is called in startup
"""
# Get the uvicorn access logger
access_logger = logging.getLogger("uvicorn.access")
# Remove existing handlers to avoid duplicate logs
for handler in access_logger.handlers[:]:
access_logger.removeHandler(handler)
# Create new handler with our custom formatter that includes timestamp and log level
handler = logging.StreamHandler(sys.stdout)
access_formatter = AccessLogFormatter("%(asctime)s | %(levelname)-8s | %(message)s")
handler.setFormatter(access_formatter)
# Add the handler to uvicorn access logger
access_logger.addHandler(handler)
access_logger.setLevel(logging.INFO)
access_logger.propagate = False
return access_logger

View File

@@ -5,7 +5,10 @@ from dotenv import load_dotenv
load_dotenv()
from app.core.application import create_app
from app.log.logger import get_main_logger
from app.log.logger import get_main_logger, setup_access_logging
# Setup access logging with API key redaction when app is imported (for CLI usage)
setup_access_logging()
app = create_app()