Merge pull request #5166 from BerriAI/litellm_log_key_created_slack

[Feat-Security] Send Slack Alert when CRUD ops done on Virtual Keys, Teams, Internal Users
This commit is contained in:
Ishaan Jaff 2024-08-12 12:18:04 -07:00 committed by GitHub
commit c401970522
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 119 additions and 1 deletions

View file

@ -26,7 +26,13 @@ from litellm.caching import DualCache
from litellm.integrations.custom_logger import CustomLogger
from litellm.litellm_core_utils.litellm_logging import Logging
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler
from litellm.proxy._types import AlertType, CallInfo, UserAPIKeyAuth, WebhookEvent
from litellm.proxy._types import (
AlertType,
CallInfo,
UserAPIKeyAuth,
VirtualKeyEvent,
WebhookEvent,
)
from litellm.types.router import LiteLLM_Params
from .email_templates.templates import *
@ -1788,3 +1794,46 @@ Model Info:
verbose_proxy_logger.error("Error sending weekly spend report %s", e)
pass
async def send_virtual_key_event_slack(
self,
key_event: VirtualKeyEvent,
event_name: str,
):
"""
Helper to send fallback statistics from prometheus server -> to slack
This runs once per day and sends an overview of all the fallback statistics
"""
try:
message = f"`{event_name}`\n"
key_event_dict = key_event.model_dump()
# Add Created by information first
message += "*Action Done by:*\n"
for key, value in key_event_dict.items():
if "created_by" in key:
message += f"{key}: `{value}`\n"
# Add args sent to function in the alert
message += "\n*Arguments passed:*\n"
request_kwargs = key_event.request_kwargs
for key, value in request_kwargs.items():
if key == "user_api_key_dict":
continue
message += f"{key}: `{value}`\n"
await self.send_alert(
message=message,
level="High",
alert_type="fallback_reports",
alerting_metadata={},
)
except Exception as e:
verbose_proxy_logger.error(
"Error sending send_virtual_key_event_slack %s", e
)
return

View file

@ -1754,3 +1754,10 @@ class SSOUserDefinedValues(TypedDict):
user_role: Optional[str]
max_budget: Optional[float]
budget_duration: Optional[str]
class VirtualKeyEvent(LiteLLMBase):
created_by_user_id: str
created_by_user_role: str
created_by_key_alias: Optional[str]
request_kwargs: dict

View file

@ -26,6 +26,7 @@ import litellm
from litellm._logging import verbose_proxy_logger
from litellm.proxy._types import *
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.management_helpers.utils import management_endpoint_wrapper
from litellm.proxy.utils import _duration_in_seconds
router = APIRouter()
@ -37,6 +38,7 @@ router = APIRouter()
dependencies=[Depends(user_api_key_auth)],
response_model=GenerateKeyResponse,
)
@management_endpoint_wrapper
async def generate_key_fn(
data: GenerateKeyRequest,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
@ -278,6 +280,7 @@ async def generate_key_fn(
@router.post(
"/key/update", tags=["key management"], dependencies=[Depends(user_api_key_auth)]
)
@management_endpoint_wrapper
async def update_key_fn(
request: Request,
data: UpdateKeyRequest,
@ -398,6 +401,7 @@ async def update_key_fn(
@router.post(
"/key/delete", tags=["key management"], dependencies=[Depends(user_api_key_auth)]
)
@management_endpoint_wrapper
async def delete_key_fn(
data: KeyRequest,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),

View file

@ -23,6 +23,7 @@ from litellm.proxy._types import ( # key request types; user request types; tea
UpdateTeamRequest,
UpdateUserRequest,
UserAPIKeyAuth,
VirtualKeyEvent,
)
from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
from litellm.proxy.utils import PrismaClient
@ -186,6 +187,56 @@ def _delete_customer_id_from_cache(kwargs):
pass
async def send_management_endpoint_alert(
request_kwargs: dict,
user_api_key_dict: UserAPIKeyAuth,
function_name: str,
):
"""
Sends a slack alert when:
- A virtual key is created, updated, or deleted
- An internal user is created, updated, or deleted
- A team is created, updated, or deleted
"""
from litellm.proxy.proxy_server import premium_user, proxy_logging_obj
if premium_user is not True:
return
management_function_to_event_name = {
"generate_key_fn": "New Virtual Key Created",
"update_key_fn": "Virtual Key Updated",
"delete_key_fn": "Virtual Key Deleted",
# Team events
"new_team": "New Team Created",
"update_team": "Team Updated",
"delete_team": "Team Deleted",
# Internal User events
"new_user": "New Internal User Created",
"user_update": "Internal User Updated",
"delete_user": "Internal User Deleted",
}
if (
proxy_logging_obj is not None
and proxy_logging_obj.slack_alerting_instance is not None
):
# Virtual Key Events
if function_name in management_function_to_event_name:
key_event = VirtualKeyEvent(
created_by_user_id=user_api_key_dict.user_id or "Unknown",
created_by_user_role=user_api_key_dict.user_role or "Unknown",
created_by_key_alias=user_api_key_dict.key_alias,
request_kwargs=request_kwargs,
)
event_name = management_function_to_event_name[function_name]
await proxy_logging_obj.slack_alerting_instance.send_virtual_key_event_slack(
key_event=key_event, event_name=event_name
)
def management_endpoint_wrapper(func):
"""
This wrapper does the following:
@ -207,6 +258,13 @@ def management_endpoint_wrapper(func):
user_api_key_dict: UserAPIKeyAuth = (
kwargs.get("user_api_key_dict") or UserAPIKeyAuth()
)
await send_management_endpoint_alert(
request_kwargs=kwargs,
user_api_key_dict=user_api_key_dict,
function_name=func.__name__,
)
_http_request: Request = kwargs.get("http_request")
parent_otel_span = user_api_key_dict.parent_otel_span
if parent_otel_span is not None: