feat: support whitelist filtering in AuditLoggingMiddleware (#22515)
Add AUDIT_INCLUDED_PATHS env var for whitelist-based audit filtering. When set, only matching paths are audited and AUDIT_EXCLUDED_PATHS is ignored. Auth endpoints (signin/signout/signup) are always logged regardless of filtering mode.
This commit is contained in:
@@ -964,6 +964,12 @@ AUDIT_EXCLUDED_PATHS = os.getenv("AUDIT_EXCLUDED_PATHS", "/chats,/chat,/folders"
|
|||||||
AUDIT_EXCLUDED_PATHS = [path.strip() for path in AUDIT_EXCLUDED_PATHS]
|
AUDIT_EXCLUDED_PATHS = [path.strip() for path in AUDIT_EXCLUDED_PATHS]
|
||||||
AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS]
|
AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS]
|
||||||
|
|
||||||
|
# Comma separated list of urls to include in audit (whitelist mode)
|
||||||
|
# When set, only these paths are audited and AUDIT_EXCLUDED_PATHS is ignored
|
||||||
|
AUDIT_INCLUDED_PATHS = os.getenv("AUDIT_INCLUDED_PATHS", "").split(",")
|
||||||
|
AUDIT_INCLUDED_PATHS = [path.strip() for path in AUDIT_INCLUDED_PATHS]
|
||||||
|
AUDIT_INCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_INCLUDED_PATHS if path]
|
||||||
|
|
||||||
|
|
||||||
####################################
|
####################################
|
||||||
# OPENTELEMETRY
|
# OPENTELEMETRY
|
||||||
|
|||||||
@@ -467,6 +467,7 @@ from open_webui.env import (
|
|||||||
ENABLE_CUSTOM_MODEL_FALLBACK,
|
ENABLE_CUSTOM_MODEL_FALLBACK,
|
||||||
LICENSE_KEY,
|
LICENSE_KEY,
|
||||||
AUDIT_EXCLUDED_PATHS,
|
AUDIT_EXCLUDED_PATHS,
|
||||||
|
AUDIT_INCLUDED_PATHS,
|
||||||
AUDIT_LOG_LEVEL,
|
AUDIT_LOG_LEVEL,
|
||||||
CHANGELOG,
|
CHANGELOG,
|
||||||
REDIS_URL,
|
REDIS_URL,
|
||||||
@@ -1581,6 +1582,7 @@ if audit_level != AuditLevel.NONE:
|
|||||||
AuditLoggingMiddleware,
|
AuditLoggingMiddleware,
|
||||||
audit_level=audit_level,
|
audit_level=audit_level,
|
||||||
excluded_paths=AUDIT_EXCLUDED_PATHS,
|
excluded_paths=AUDIT_EXCLUDED_PATHS,
|
||||||
|
included_paths=AUDIT_INCLUDED_PATHS,
|
||||||
max_body_size=MAX_BODY_LOG_SIZE,
|
max_body_size=MAX_BODY_LOG_SIZE,
|
||||||
)
|
)
|
||||||
##################################
|
##################################
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from asgiref.typing import (
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
|
|
||||||
from open_webui.env import AUDIT_LOG_LEVEL, MAX_BODY_LOG_SIZE
|
from open_webui.env import AUDIT_LOG_LEVEL, AUDIT_INCLUDED_PATHS, MAX_BODY_LOG_SIZE
|
||||||
from open_webui.utils.auth import get_current_user, get_http_authorization_cred
|
from open_webui.utils.auth import get_current_user, get_http_authorization_cred
|
||||||
from open_webui.models.users import UserModel
|
from open_webui.models.users import UserModel
|
||||||
|
|
||||||
@@ -129,15 +129,23 @@ class AuditLoggingMiddleware:
|
|||||||
app: ASGI3Application,
|
app: ASGI3Application,
|
||||||
*,
|
*,
|
||||||
excluded_paths: Optional[list[str]] = None,
|
excluded_paths: Optional[list[str]] = None,
|
||||||
|
included_paths: Optional[list[str]] = None,
|
||||||
max_body_size: int = MAX_BODY_LOG_SIZE,
|
max_body_size: int = MAX_BODY_LOG_SIZE,
|
||||||
audit_level: AuditLevel = AuditLevel.NONE,
|
audit_level: AuditLevel = AuditLevel.NONE,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.app = app
|
self.app = app
|
||||||
self.audit_logger = AuditLogger(logger)
|
self.audit_logger = AuditLogger(logger)
|
||||||
self.excluded_paths = excluded_paths or []
|
self.excluded_paths = excluded_paths or []
|
||||||
|
self.included_paths = included_paths or []
|
||||||
self.max_body_size = max_body_size
|
self.max_body_size = max_body_size
|
||||||
self.audit_level = audit_level
|
self.audit_level = audit_level
|
||||||
|
|
||||||
|
if self.included_paths and self.excluded_paths:
|
||||||
|
logger.warning(
|
||||||
|
"Both AUDIT_INCLUDED_PATHS and AUDIT_EXCLUDED_PATHS are set. "
|
||||||
|
"AUDIT_INCLUDED_PATHS (whitelist) takes precedence."
|
||||||
|
)
|
||||||
|
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
scope: ASGIScope,
|
scope: ASGIScope,
|
||||||
@@ -226,7 +234,16 @@ class AuditLoggingMiddleware:
|
|||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# match either /api/<resource>/...(for the endpoint /api/chat case) or /api/v1/<resource>/...
|
# Whitelist mode: only log paths that match included_paths
|
||||||
|
if self.included_paths:
|
||||||
|
pattern = re.compile(
|
||||||
|
r"^/api(?:/v1)?/(" + "|".join(self.included_paths) + r")\b"
|
||||||
|
)
|
||||||
|
if not pattern.match(request.url.path):
|
||||||
|
return True # Skip: path not in whitelist
|
||||||
|
return False # Do NOT skip: path is in whitelist
|
||||||
|
|
||||||
|
# Blacklist mode: skip paths that match excluded_paths
|
||||||
pattern = re.compile(
|
pattern = re.compile(
|
||||||
r"^/api(?:/v1)?/(" + "|".join(self.excluded_paths) + r")\b"
|
r"^/api(?:/v1)?/(" + "|".join(self.excluded_paths) + r")\b"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user