diff --git a/docs/my-website/docs/proxy/multiple_admins.md b/docs/my-website/docs/proxy/multiple_admins.md new file mode 100644 index 0000000000..388df0d603 --- /dev/null +++ b/docs/my-website/docs/proxy/multiple_admins.md @@ -0,0 +1,89 @@ +# ✨ Attribute Management changes to Users + +Call management endpoints on behalf of a user. (Useful when connecting proxy to your development platform). + +:::info +Requires Enterprise License for usage. +::: + +## Set `LiteLLM-Changed-By` in request headers + +Set the 'user_id' in request headers, when calling a management endpoint. [View Full List](https://litellm-api.up.railway.app/#/team%20management). + +- Update Team budget with master key. +- Attribute change to 'krrish@berri.ai'. + +**👉 Key change:** Passing `-H 'LiteLLM-Changed-By: krrish@berri.ai'` + +```shell +curl -X POST 'http://0.0.0.0:4000/team/update' \ + -H 'Authorization: Bearer sk-1234' \ + -H 'LiteLLM-Changed-By: krrish@berri.ai' \ + -H 'Content-Type: application/json' \ + -d '{ + "team_id" : "8bf18b11-7f52-4717-8e1f-7c65f9d01e52", + "max_budget": 2000 + }' +``` + +## Emitted Audit Log + +```bash +{ + "id": "bd136c28-edd0-4cb6-b963-f35464cf6f5a", + "updated_at": "2024-06-08 23:41:14.793", + "changed_by": "krrish@berri.ai", # 👈 CHANGED BY + "changed_by_api_key": "88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b", + "action": "updated", + "table_name": "LiteLLM_TeamTable", + "object_id": "8bf18b11-7f52-4717-8e1f-7c65f9d01e52", + "before_value": { + "spend": 0, + "max_budget": 0, + }, + "updated_values": { + "team_id": "8bf18b11-7f52-4717-8e1f-7c65f9d01e52", + "max_budget": 2000 # 👈 CHANGED TO + }, + } +``` + +## API SPEC of Audit Log + + +### `id` +- **Type:** `String` +- **Description:** This is the unique identifier for each audit log entry. It is automatically generated as a UUID (Universally Unique Identifier) by default. + +### `updated_at` +- **Type:** `DateTime` +- **Description:** This field stores the timestamp of when the audit log entry was created or updated. It is automatically set to the current date and time by default. + +### `changed_by` +- **Type:** `String` +- **Description:** The `user_id` that performed the audited action. If `LiteLLM-Changed-By` Header is passed then `changed_by=` + +### `changed_by_api_key` +- **Type:** `String` +- **Description:** This field stores the hashed API key that was used to perform the audited action. If left blank, it defaults to an empty string. + +### `action` +- **Type:** `String` +- **Description:** The type of action that was performed. One of "create", "update", or "delete". + +### `table_name` +- **Type:** `String` +- **Description:** This field stores the name of the table that was affected by the audited action. It can be one of the following values: `LiteLLM_TeamTable`, `LiteLLM_UserTable`, `LiteLLM_VerificationToken` + + +### `object_id` +- **Type:** `String` +- **Description:** This field stores the ID of the object that was affected by the audited action. It can be the key ID, team ID, user ID + +### `before_value` +- **Type:** `Json?` +- **Description:** This field stores the value of the row before the audited action was performed. It is optional and can be null. + +### `updated_values` +- **Type:** `Json?` +- **Description:** This field stores the values of the row that were updated after the audited action was performed \ No newline at end of file diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index 88ac5e6cdb..b6b597d305 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -55,6 +55,7 @@ const sidebars = { }, "proxy/ui", "proxy/email", + "proxy/multiple_admins", "proxy/team_based_routing", "proxy/customer_routing", "proxy/token_auth", diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 6409773e38..548536e11c 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1305,6 +1305,7 @@ class LiteLLM_AuditLogs(LiteLLMBase): id: str updated_at: datetime changed_by: str + changed_by_api_key: Optional[str] = None action: Literal["created", "updated", "deleted"] table_name: Literal[ LitellmTableNames.TEAM_TABLE_NAME, diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 133124db8e..5a068dd683 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -6426,7 +6426,10 @@ async def supported_openai_params(model: str): async def generate_key_fn( data: GenerateKeyRequest, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), - Authorization: Optional[str] = Header(None), + litellm_changed_by: Optional[str] = Header( + None, + description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", + ), ): """ Generate an API key based on the provided data. @@ -6611,8 +6614,10 @@ async def generate_key_fn( request_data=LiteLLM_AuditLogs( id=str(uuid.uuid4()), updated_at=datetime.now(timezone.utc), - changed_by=user_api_key_dict.user_id + 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.KEY_TABLE_NAME, object_id=response.get("token_id", ""), action="created", @@ -6654,6 +6659,10 @@ async def update_key_fn( request: Request, data: UpdateKeyRequest, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), + litellm_changed_by: Optional[str] = Header( + None, + description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", + ), ): """ Update an existing key @@ -6714,8 +6723,10 @@ async def update_key_fn( request_data=LiteLLM_AuditLogs( id=str(uuid.uuid4()), updated_at=datetime.now(timezone.utc), - changed_by=user_api_key_dict.user_id + 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.KEY_TABLE_NAME, object_id=data.key, action="updated", @@ -6751,6 +6762,10 @@ async def update_key_fn( async def delete_key_fn( data: KeyRequest, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), + litellm_changed_by: Optional[str] = Header( + None, + description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", + ), ): """ Delete a key from the key management system. @@ -6804,8 +6819,10 @@ async def delete_key_fn( request_data=LiteLLM_AuditLogs( id=str(uuid.uuid4()), updated_at=datetime.now(timezone.utc), - changed_by=user_api_key_dict.user_id + 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.KEY_TABLE_NAME, object_id=key, action="deleted", @@ -9809,6 +9826,10 @@ async def delete_end_user( async def new_team( data: NewTeamRequest, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), + litellm_changed_by: Optional[str] = Header( + None, + description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", + ), ): """ Allow users to create a new team. Apply user permissions to their team. @@ -9983,7 +10004,10 @@ async def new_team( request_data=LiteLLM_AuditLogs( id=str(uuid.uuid4()), updated_at=datetime.now(timezone.utc), - changed_by=user_api_key_dict.user_id or litellm_proxy_admin_name, + 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.TEAM_TABLE_NAME, object_id=data.team_id, action="created", @@ -10037,6 +10061,10 @@ async def create_audit_log_for_update(request_data: LiteLLM_AuditLogs): async def update_team( data: UpdateTeamRequest, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), + litellm_changed_by: Optional[str] = Header( + None, + description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", + ), ): """ Use `/team/member_add` AND `/team/member/delete` to add/remove new team members @@ -10114,7 +10142,10 @@ async def update_team( request_data=LiteLLM_AuditLogs( id=str(uuid.uuid4()), updated_at=datetime.now(timezone.utc), - changed_by=user_api_key_dict.user_id or litellm_proxy_admin_name, + 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.TEAM_TABLE_NAME, object_id=data.team_id, action="updated", @@ -10356,6 +10387,10 @@ async def team_member_delete( async def delete_team( data: DeleteTeamRequest, user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), + litellm_changed_by: Optional[str] = Header( + None, + description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", + ), ): """ delete team and associated team keys @@ -10407,8 +10442,10 @@ async def delete_team( request_data=LiteLLM_AuditLogs( id=str(uuid.uuid4()), updated_at=datetime.now(timezone.utc), - changed_by=user_api_key_dict.user_id + 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.TEAM_TABLE_NAME, object_id=team_id, action="deleted", diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index 8843761327..2444a407fb 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -247,12 +247,13 @@ model LiteLLM_InvitationLink { model LiteLLM_AuditLog { - id String @id @default(uuid()) - updated_at DateTime @default(now()) - changed_by String // user or system that performed the action - action String // create, update, delete - table_name String // on of LitellmTableNames.TEAM_TABLE_NAME, LitellmTableNames.USER_TABLE_NAME, LitellmTableNames.PROXY_MODEL_TABLE_NAME, - object_id String // id of the object being audited. This can be the key id, team id, user id, model id - before_value Json? // value of the row - updated_values Json? // value of the row after change + id String @id @default(uuid()) + updated_at DateTime @default(now()) + changed_by String @default("") // user or system that performed the action + changed_by_api_key String @default("") // api key hash that performed the action + action String // create, update, delete + table_name String // on of LitellmTableNames.TEAM_TABLE_NAME, LitellmTableNames.USER_TABLE_NAME, LitellmTableNames.PROXY_MODEL_TABLE_NAME, + object_id String // id of the object being audited. This can be the key id, team id, user id, model id + before_value Json? // value of the row + updated_values Json? // value of the row after change } \ No newline at end of file diff --git a/schema.prisma b/schema.prisma index 7cc688ee8e..7da3f9cd10 100644 --- a/schema.prisma +++ b/schema.prisma @@ -247,12 +247,13 @@ model LiteLLM_InvitationLink { model LiteLLM_AuditLog { - id String @id @default(uuid()) - updated_at DateTime @default(now()) - changed_by String // user or system that performed the action - action String // create, update, delete - table_name String // on of LitellmTableNames.TEAM_TABLE_NAME, LitellmTableNames.USER_TABLE_NAME, LitellmTableNames.PROXY_MODEL_TABLE_NAME, - object_id String // id of the object being audited. This can be the key id, team id, user id, model id - before_value Json? // value of the row - updated_values Json? // value of the row after change + id String @id @default(uuid()) + updated_at DateTime @default(now()) + changed_by String @default("") // user or system that performed the action + changed_by_api_key String @default("") // api key hash that performed the action + action String // create, update, delete + table_name String // on of LitellmTableNames.TEAM_TABLE_NAME, LitellmTableNames.USER_TABLE_NAME, LitellmTableNames.PROXY_MODEL_TABLE_NAME, + object_id String // id of the object being audited. This can be the key id, team id, user id, model id + before_value Json? // value of the row + updated_values Json? // value of the row after change } \ No newline at end of file