security: Implemented API key redaction for secure logging

- Add redact_key_for_logging() helper function to show only first/last 6 chars
- Fix API key exposure in app/service/key/key_manager.py line 68
- Apply key redaction across all Python files with API key logging
- Standardize logging security across 17 files including routers, services, handlers
This commit is contained in:
Shuai Lin
2025-07-21 02:03:29 +08:00
parent b25cf7d978
commit 6abda7d902
17 changed files with 78 additions and 46 deletions
+6 -5
View File
@@ -14,6 +14,7 @@ from app.log.logger import get_gemini_logger
from app.service.client.api_client import GeminiApiClient
from app.service.key.key_manager import KeyManager
from app.database.services import add_error_log, add_request_log, get_file_api_key
from app.utils.helpers import redact_key_for_logging
logger = get_gemini_logger()
@@ -296,10 +297,10 @@ class GeminiChatService:
logger.info(f"Request contains file references: {file_names}")
file_api_key = await get_file_api_key(file_names[0])
if file_api_key:
logger.info(f"Found API key for file {file_names[0]}: {file_api_key[:8]}...{file_api_key[-4:]}")
logger.info(f"Found API key for file {file_names[0]}: {redact_key_for_logging(file_api_key)}")
api_key = file_api_key # 使用文件的 API key
else:
logger.warning(f"No API key found for file {file_names[0]}, using default key: {api_key[:8]}...{api_key[-4:]}")
logger.warning(f"No API key found for file {file_names[0]}, using default key: {redact_key_for_logging(api_key)}")
payload = _build_payload(model, request)
start_time = time.perf_counter()
@@ -402,10 +403,10 @@ class GeminiChatService:
logger.info(f"Request contains file references: {file_names}")
file_api_key = await get_file_api_key(file_names[0])
if file_api_key:
logger.info(f"Found API key for file {file_names[0]}: {file_api_key[:8]}...{file_api_key[-4:]}")
logger.info(f"Found API key for file {file_names[0]}: {redact_key_for_logging(file_api_key)}")
api_key = file_api_key # 使用文件的 API key
else:
logger.warning(f"No API key found for file {file_names[0]}, using default key: {api_key[:8]}...{api_key[-4:]}")
logger.warning(f"No API key found for file {file_names[0]}, using default key: {redact_key_for_logging(api_key)}")
retries = 0
max_retries = settings.MAX_RETRIES
@@ -472,7 +473,7 @@ class GeminiChatService:
api_key = await self.key_manager.handle_api_failure(current_attempt_key, retries)
if api_key:
logger.info(f"Switched to new API key: {api_key}")
logger.info(f"Switched to new API key: {redact_key_for_logging(api_key)}")
else:
logger.error(f"No valid API key available after {retries} retries.")
break
@@ -14,6 +14,7 @@ from app.log.logger import get_gemini_logger
from app.service.client.api_client import GeminiApiClient
from app.service.key.key_manager import KeyManager
from app.database.services import add_error_log, add_request_log
from app.utils.helpers import redact_key_for_logging
logger = get_gemini_logger()
@@ -340,7 +341,7 @@ class GeminiChatService:
api_key = await self.key_manager.handle_api_failure(current_attempt_key, retries)
if api_key:
logger.info(f"Switched to new API key: {api_key}")
logger.info(f"Switched to new API key: {redact_key_for_logging(api_key)}")
else:
logger.error(f"No valid API key available after {retries} retries.")
break
+2 -1
View File
@@ -12,6 +12,7 @@ from app.config.config import settings
from app.database import services as db_services
from app.database.models import FileState
from app.log.logger import get_files_logger
from app.utils.helpers import redact_key_for_logging
logger = get_files_logger()
@@ -144,7 +145,7 @@ class FileUploadHandler:
sha256_hash=file_data.get("sha256Hash"),
user_token=session_info["user_token"]
)
logger.info(f"Created file record: name={real_file_name}, api_key={session_info['api_key'][:8]}...{session_info['api_key'][-4:]}")
logger.info(f"Created file record: name={real_file_name}, api_key={redact_key_for_logging(session_info['api_key'])}")
else:
logger.warning(f"No upload session found for URL: {upload_url}")
else:
+5 -4
View File
@@ -13,6 +13,7 @@ from app.database.models import FileState
from app.domain.file_models import FileMetadata, ListFilesResponse
from fastapi import HTTPException
from app.log.logger import get_files_logger
from app.utils.helpers import redact_key_for_logging
from app.service.client.api_client import GeminiApiClient
from app.service.key.key_manager import get_key_manager_instance
@@ -102,7 +103,7 @@ class FilesService:
# 儲存上傳資訊到 headers 中,供後續使用
# 不在這裡創建數據庫記錄,等到上傳完成後再創建
logger.info(f"Upload initialized with API key: {api_key[:8]}...{api_key[-4:]}")
logger.info(f"Upload initialized with API key: {redact_key_for_logging(api_key)}")
# 解析响应 - 初始化响应可能是空的
response_data = {}
@@ -133,7 +134,7 @@ class FilesService:
"created_at": datetime.now(timezone.utc),
"upload_url": upload_url
}
logger.info(f"Stored upload session for upload_id={upload_id}: api_key={api_key[:8]}...{api_key[-4:]}")
logger.info(f"Stored upload session for upload_id={upload_id}: api_key={redact_key_for_logging(api_key)}")
logger.debug(f"Total active sessions: {len(_upload_sessions)}")
else:
logger.warning(f"No upload_id found in upload URL: {upload_url}")
@@ -202,7 +203,7 @@ class FilesService:
# 先嘗試直接查找
session = _upload_sessions.get(key)
if session:
logger.debug(f"Found session by direct key {key}")
logger.debug(f"Found session by direct key {redact_key_for_logging(key)}")
return session
# 如果是 URL,嘗試提取 upload_id
@@ -217,7 +218,7 @@ class FilesService:
logger.debug(f"Found session by upload_id {upload_id} from URL")
return session
logger.debug(f"No session found for key: {key}")
logger.debug(f"No session found for key: {redact_key_for_logging(key)}")
return None
async def get_file(self, file_name: str, user_token: str) -> FileMetadata:
+5 -4
View File
@@ -4,6 +4,7 @@ from typing import Dict, Union
from app.config.config import settings
from app.log.logger import get_key_manager_logger
from app.utils.helpers import redact_key_for_logging
logger = get_key_manager_logger()
@@ -65,7 +66,7 @@ class KeyManager:
async with self.failure_count_lock:
if key in self.key_failure_counts:
self.key_failure_counts[key] = 0
logger.info(f"Reset failure count for key: {key}")
logger.info(f"Reset failure count for key: {redact_key_for_logging(key)}")
return True
logger.warning(
f"Attempt to reset failure count for non-existent key: {key}"
@@ -77,7 +78,7 @@ class KeyManager:
async with self.vertex_failure_count_lock:
if key in self.vertex_key_failure_counts:
self.vertex_key_failure_counts[key] = 0
logger.info(f"Reset failure count for Vertex key: {key}")
logger.info(f"Reset failure count for Vertex key: {redact_key_for_logging(key)}")
return True
logger.warning(
f"Attempt to reset failure count for non-existent Vertex key: {key}"
@@ -116,7 +117,7 @@ class KeyManager:
self.key_failure_counts[api_key] += 1
if self.key_failure_counts[api_key] >= self.MAX_FAILURES:
logger.warning(
f"API key {api_key} has failed {self.MAX_FAILURES} times"
f"API key {redact_key_for_logging(api_key)} has failed {self.MAX_FAILURES} times"
)
if retries < settings.MAX_RETRIES:
return await self.get_next_working_key()
@@ -129,7 +130,7 @@ class KeyManager:
self.vertex_key_failure_counts[api_key] += 1
if self.vertex_key_failure_counts[api_key] >= self.MAX_FAILURES:
logger.warning(
f"Vertex Express API key {api_key} has failed {self.MAX_FAILURES} times"
f"Vertex Express API key {redact_key_for_logging(api_key)} has failed {self.MAX_FAILURES} times"
)
def get_fail_count(self, key: str) -> int:
@@ -13,6 +13,7 @@ from app.database.services import (
from app.domain.openai_models import ChatRequest, ImageGenerationRequest
from app.service.client.api_client import OpenaiApiClient
from app.service.key.key_manager import KeyManager
from app.utils.helpers import redact_key_for_logging
from app.log.logger import get_openai_compatible_logger
logger = get_openai_compatible_logger()
@@ -159,7 +160,7 @@ class OpenAICompatiableService:
current_attempt_key, retries
)
if api_key:
logger.info(f"Switched to new API key: {api_key}")
logger.info(f"Switched to new API key: {redact_key_for_logging(api_key)}")
else:
logger.error(
f"No valid API key available after {retries} retries."