feat(internal_user_endpoints.py): emit audit log on /user/new event

This commit is contained in:
Krrish Dholakia 2025-03-13 16:47:58 -07:00
parent 53f9df5506
commit 5cfae0e98a
4 changed files with 81 additions and 13 deletions

View file

@ -5,13 +5,5 @@ model_list:
aws_region_name: "us-east-1"
litellm_credential_name: "azure"
credential_list:
- credential_name: azure
credential_values:
api_key: os.environ/AZURE_API_KEY
api_base: os.environ/AZURE_API_BASE
credential_info:
description: "Azure API Key and Base URL"
type: "azure"
required: true
default: "azure"
litellm_settings:
store_audit_logs: true

View file

@ -1584,7 +1584,7 @@ class NewOrganizationResponse(LiteLLM_OrganizationTable):
class LiteLLM_UserTable(LiteLLMPydanticObjectBase):
user_id: str
max_budget: Optional[float]
max_budget: Optional[float] = None
spend: float = 0.0
model_max_budget: Optional[Dict] = {}
model_spend: Optional[Dict] = {}
@ -1677,12 +1677,15 @@ class LiteLLM_ErrorLogs(LiteLLMPydanticObjectBase):
endTime: Union[str, datetime, None]
AUDIT_ACTIONS = Literal["created", "updated", "deleted", "blocked"]
class LiteLLM_AuditLogs(LiteLLMPydanticObjectBase):
id: str
updated_at: datetime
changed_by: Optional[Any] = None
changed_by_api_key: Optional[str] = None
action: Literal["created", "updated", "deleted", "blocked"]
action: AUDIT_ACTIONS
table_name: Literal[
LitellmTableNames.TEAM_TABLE_NAME,
LitellmTableNames.USER_TABLE_NAME,

View file

@ -53,8 +53,11 @@ def decrypt_value_helper(value: str):
# if it's not str - do not decrypt it, return the value
return value
except Exception as e:
import traceback
traceback.print_stack()
verbose_proxy_logger.error(
f"Error decrypting value, Did your master_key/salt key change recently? : {value}\nError: {str(e)}\nSet permanent salt key - https://docs.litellm.ai/docs/proxy/prod#5-set-litellm-salt-key"
f"Error decrypting value, Did your master_key/salt key change recently? \nError: {str(e)}\nSet permanent salt key - https://docs.litellm.ai/docs/proxy/prod#5-set-litellm-salt-key"
)
# [Non-Blocking Exception. - this should not block decrypting other values]
pass

View file

@ -29,12 +29,53 @@ from litellm.proxy.management_endpoints.key_management_endpoints import (
generate_key_helper_fn,
prepare_metadata_fields,
)
from litellm.proxy.management_helpers.audit_logs import create_audit_log_for_update
from litellm.proxy.management_helpers.utils import management_endpoint_wrapper
from litellm.proxy.utils import handle_exception_on_proxy
router = APIRouter()
async def create_internal_user_audit_log(
user_id: str,
action: AUDIT_ACTIONS,
litellm_changed_by: Optional[str],
user_api_key_dict: UserAPIKeyAuth,
litellm_proxy_admin_name: Optional[str],
before_value: Optional[str] = None,
after_value: Optional[str] = None,
):
"""
Create an audit log for an internal user.
Parameters:
- user_id: str - The id of the user to create the audit log for.
- action: AUDIT_ACTIONS - The action to create the audit log for.
- user_row: LiteLLM_UserTable - The user row to create the audit log for.
- litellm_changed_by: Optional[str] - The user id of the user who is changing the user.
- user_api_key_dict: UserAPIKeyAuth - The user api key dictionary.
- litellm_proxy_admin_name: Optional[str] - The name of the proxy admin.
"""
if not litellm.store_audit_logs:
return
await create_audit_log_for_update(
request_data=LiteLLM_AuditLogs(
id=str(uuid.uuid4()),
updated_at=datetime.now(timezone.utc),
changed_by=litellm_changed_by
or user_api_key_dict.user_id
or litellm_proxy_admin_name,
changed_by_api_key=user_api_key_dict.api_key,
table_name=LitellmTableNames.USER_TABLE_NAME,
object_id=user_id,
action=action,
updated_values=after_value,
before_value=before_value,
)
)
def _update_internal_new_user_params(data_json: dict, data: NewUserRequest) -> dict:
if "user_id" in data_json and data_json["user_id"] is None:
data_json["user_id"] = str(uuid.uuid4())
@ -169,6 +210,7 @@ async def new_user(
try:
from litellm.proxy.proxy_server import (
general_settings,
litellm_proxy_admin_name,
prisma_client,
proxy_logging_obj,
)
@ -254,6 +296,34 @@ async def new_user(
)
)
try:
if prisma_client is None:
raise Exception(CommonProxyErrors.db_not_connected_error.value)
user_row: BaseModel = await prisma_client.db.litellm_usertable.find_first(
where={"user_id": response["user_id"]}
)
user_row_litellm_typed = LiteLLM_UserTable(
**user_row.model_dump(exclude_none=True)
)
asyncio.create_task(
create_internal_user_audit_log(
user_id=user_row_litellm_typed.user_id,
action="created",
litellm_changed_by=user_api_key_dict.user_id,
user_api_key_dict=user_api_key_dict,
litellm_proxy_admin_name=litellm_proxy_admin_name,
before_value=None,
after_value=user_row_litellm_typed.model_dump_json(
exclude_none=True
),
)
)
except Exception as e:
verbose_proxy_logger.warning(
"Unable to create audit log for user on `/user/new` - {}".format(str(e))
)
return NewUserResponse(
key=response.get("token", ""),
expires=response.get("expires", None),