diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml index 9436e8af0f..718e22806d 100644 --- a/litellm/proxy/_new_secret_config.yaml +++ b/litellm/proxy/_new_secret_config.yaml @@ -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" \ No newline at end of file +litellm_settings: + store_audit_logs: true diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index a9fe6517ea..8611fbd716 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -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, diff --git a/litellm/proxy/common_utils/encrypt_decrypt_utils.py b/litellm/proxy/common_utils/encrypt_decrypt_utils.py index 7be60d9dc6..ec9279a089 100644 --- a/litellm/proxy/common_utils/encrypt_decrypt_utils.py +++ b/litellm/proxy/common_utils/encrypt_decrypt_utils.py @@ -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 diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 85b58ba596..aa4c3c343b 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -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),